Merge pull request #4337 from Polymer/2.0-issue-blitz

Fixes a number of issues
This commit is contained in:
Kevin Schaaf 2017-02-27 11:03:15 -08:00 committed by GitHub
commit 106b53cdd7
10 changed files with 1313 additions and 208 deletions

View File

@ -161,6 +161,7 @@ Polymer 2.0 elements will stamp their templates into shadow roots created using
* <a name="breaking-slot-slot"></a>Selection of distributed content into named slots must use `slot="..."` rather than tag/class/attributes selected by `<content>`
* <a name="breaking-redistribution"></a>Re-distributing content by placing a `<slot>` into an element that itself has named slots requires placing a `name` attribute on the `<slot>` to indicate what content _it_ selects from its host children, and placing a `slot` attribute to indicate where its selected content should be slotted into its parent
* <a name="breaking-async-distribution"></a>In the V1 "Shady DOM" shim, initial distribution of children into `<slot>` is asynchronous (microtask) to creating the `shadowRoot`, meaning distribution occurs after observers/`ready` (in Polymer 1.0's shim, initial distribution occurred before `ready`). In order to force distribution synchronously, call `ShadyDOM.flush()`.
* <a name="breaking-observe-nodes-flush"></a>Calling `Polymer.dom.flush` no longer results in callbacks registered with `Polymer.dom.observeNodes` being called. Instead, the object returned from `Polymer.dom.observeNodes` now contains a `flush` method which can be used to immediately call the registered callback if any changes are pending.
#### Scoped styling

View File

@ -8,30 +8,11 @@ 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
-->
<link rel="import" href="../utils/boot.html">
<link rel="import" href="../utils/array-splice.html">
<link rel="import" href="../utils/async.html">
<link rel="import" href="../utils/flattened-nodes-observer.html">
<link rel="import" href="../utils/flush.html">
<script>
(function() {
function isSlot(node) {
return (node.localName === 'slot');
}
function getEffectiveNodes(node) {
if (isSlot(node)) {
return node.assignedNodes({flatten: true});
} else {
return Array.from(node.childNodes).map(node => {
if (isSlot(node)) {
return node.assignedNodes({flatten: true});
} else {
return [node];
}
}).reduce((a, b) => a.concat(b), []);
}
}
let matchesSelector = (function() {
let p = Element.prototype;
let normalizedMatchesSelector = p.matches || p.matchesSelector ||
@ -43,149 +24,6 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
})();
class EffectiveNodesObserver {
constructor(target, callback) {
/** @type {MutationObserver} */
this._shadyChildrenObserver = null;
/** @type {MutationObserver} */
this._nativeChildrenObserver = null;
this._connected = false;
this._target = target;
this.callback = callback;
this._effectiveNodes = [];
this._observer = null;
this._scheduled = false;
this._boundSchedule = () => {
this._schedule();
}
this._connect();
this._schedule();
}
_connect() {
if (isSlot(this._target)) {
this._listenSlots([this._target]);
} else {
this._listenSlots(this._target.children);
if (window.ShadyDOM) {
this._shadyChildrenObserver =
ShadyDOM.observeChildren(this._target, (mutations) => {
this._processMutations(mutations);
});
} else {
this._nativeChildrenObserver =
new MutationObserver((mutations) => {
this._processMutations(mutations);
});
this._nativeChildrenObserver.observe(this._target, {childList: true});
}
}
this._connected = true;
}
disconnect() {
if (isSlot(this._target)) {
this._unlistenSlots([this._target]);
} else {
this._unlistenSlots(this._target.children);
if (window.ShadyDOM && this._shadyChildrenObserver) {
ShadyDOM.unobserveChildren(this._shadyChildrenObserver);
this._shadyChildrenObserver = null;
} else if (this._nativeChildrenObserver) {
this._nativeChildrenObserver.disconnect();
this._nativeChildrenObserver = null;
}
}
this._connected = false;
}
_schedule() {
if (!this._scheduled) {
this._scheduled = true;
Polymer.Async.microTask.run(() => this.flush());
}
}
_processMutations(mutations) {
this._processSlotMutations(mutations);
this.flush();
}
_processSlotMutations(mutations) {
if (mutations) {
for (let i=0; i < mutations.length; i++) {
let mutation = mutations[i];
if (mutation.addedNodes) {
this._listenSlots(mutation.addedNodes);
}
if (mutation.removedNodes) {
this._unlistenSlots(mutation.removedNodes);
}
}
}
}
flush() {
if (!this._connected) {
return;
}
if (window.ShadyDOM) {
ShadyDOM.flush();
}
if (this._nativeChildrenObserver) {
this._processSlotMutations(this._nativeChildrenObserver.takeRecords());
} else if (this.shadyChildrenObserver) {
this._processSlotMutations(this._shadyChildrenObserver.takeRecords());
}
this._scheduled = false;
let info = {
target: this._target,
addedNodes: [],
removedNodes: []
};
let newNodes = getEffectiveNodes(this._target);
let splices = Polymer.ArraySplice.calculateSplices(newNodes,
this._effectiveNodes);
// process removals
for (let i=0, s; (i<splices.length) && (s=splices[i]); i++) {
for (let j=0, n; (j < s.removed.length) && (n=s.removed[j]); j++) {
info.removedNodes.push(n);
}
}
// process adds
for (let i=0, s; (i<splices.length) && (s=splices[i]); i++) {
for (let j=s.index; j < s.index + s.addedCount; j++) {
info.addedNodes.push(newNodes[j]);
}
}
// update cache
this._effectiveNodes = newNodes;
if (info.addedNodes.length || info.removedNodes.length) {
this.callback.call(this._target, info);
}
}
_listenSlots(nodeList) {
for (let i=0; i < nodeList.length; i++) {
let n = nodeList[i];
if (isSlot(n)) {
n.addEventListener('slotchange', this._boundSchedule);
}
}
}
_unlistenSlots(nodeList) {
for (let i=0; i < nodeList.length; i++) {
let n = nodeList[i];
if (isSlot(n)) {
n.removeEventListener('slotchange', this._boundSchedule);
}
}
}
}
class DomApi {
constructor(node) {
@ -196,7 +34,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
observeNodes(callback) {
return new EffectiveNodesObserver(this.node, callback);
return new Polymer.FlattenedNodesObserver(this.node, callback);
}
unobserveNodes(observerHandle) {
@ -246,7 +84,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
getEffectiveChildNodes() {
return getEffectiveNodes(this.node);
return Polymer.FlattenedNodesObserver.getFlattenedNodes(this.node);
}
queryDistributedElements(selector) {
@ -354,9 +192,14 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
Polymer.dom.addDebouncer = Polymer.enqueueDebouncer;
Polymer.Settings = {
useShadow: true
};
// expose BC settings.
let settings = Polymer.Settings || {};
settings.useShadow = !(window.ShadyDOM);
settings.useNativeCSSProperties =
Boolean(!window.ShadyCSS || window.ShadyCSS.nativeCss);
settings.useNativeCustomElements =
!(window.customElements.polyfillWrapFlushCallback);
Polymer.Settings = settings;
})();
</script>

View File

@ -0,0 +1,180 @@
<!--
@license
Copyright (c) 2014 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
-->
<link rel="import" href="../utils/boot.html">
<link rel="import" href="../utils/array-splice.html">
<link rel="import" href="../utils/async.html">
<script>
(function() {
function isSlot(node) {
return (node.localName === 'slot');
}
class FlattenedNodesObserver {
static getFlattenedNodes(node) {
if (isSlot(node)) {
return node.assignedNodes({flatten: true});
} else {
return Array.from(node.childNodes).map(node => {
if (isSlot(node)) {
return node.assignedNodes({flatten: true});
} else {
return [node];
}
}).reduce((a, b) => a.concat(b), []);
}
}
constructor(target, callback) {
/** @type {MutationObserver} */
this._shadyChildrenObserver = null;
/** @type {MutationObserver} */
this._nativeChildrenObserver = null;
this._connected = false;
this._target = target;
this.callback = callback;
this._effectiveNodes = [];
this._observer = null;
this._scheduled = false;
this._boundSchedule = () => {
this._schedule();
}
this._connect();
this._schedule();
}
_connect() {
if (isSlot(this._target)) {
this._listenSlots([this._target]);
} else {
this._listenSlots(this._target.children);
if (window.ShadyDOM) {
this._shadyChildrenObserver =
ShadyDOM.observeChildren(this._target, (mutations) => {
this._processMutations(mutations);
});
} else {
this._nativeChildrenObserver =
new MutationObserver((mutations) => {
this._processMutations(mutations);
});
this._nativeChildrenObserver.observe(this._target, {childList: true});
}
}
this._connected = true;
}
disconnect() {
if (isSlot(this._target)) {
this._unlistenSlots([this._target]);
} else {
this._unlistenSlots(this._target.children);
if (window.ShadyDOM && this._shadyChildrenObserver) {
ShadyDOM.unobserveChildren(this._shadyChildrenObserver);
this._shadyChildrenObserver = null;
} else if (this._nativeChildrenObserver) {
this._nativeChildrenObserver.disconnect();
this._nativeChildrenObserver = null;
}
}
this._connected = false;
}
_schedule() {
if (!this._scheduled) {
this._scheduled = true;
Polymer.Async.microTask.run(() => this.flush());
}
}
_processMutations(mutations) {
this._processSlotMutations(mutations);
this.flush();
}
_processSlotMutations(mutations) {
if (mutations) {
for (let i=0; i < mutations.length; i++) {
let mutation = mutations[i];
if (mutation.addedNodes) {
this._listenSlots(mutation.addedNodes);
}
if (mutation.removedNodes) {
this._unlistenSlots(mutation.removedNodes);
}
}
}
}
flush() {
if (!this._connected) {
return;
}
if (window.ShadyDOM) {
ShadyDOM.flush();
}
if (this._nativeChildrenObserver) {
this._processSlotMutations(this._nativeChildrenObserver.takeRecords());
} else if (this.shadyChildrenObserver) {
this._processSlotMutations(this._shadyChildrenObserver.takeRecords());
}
this._scheduled = false;
let info = {
target: this._target,
addedNodes: [],
removedNodes: []
};
let newNodes = this.constructor.getFlattenedNodes(this._target);
let splices = Polymer.ArraySplice.calculateSplices(newNodes,
this._effectiveNodes);
// process removals
for (let i=0, s; (i<splices.length) && (s=splices[i]); i++) {
for (let j=0, n; (j < s.removed.length) && (n=s.removed[j]); j++) {
info.removedNodes.push(n);
}
}
// process adds
for (let i=0, s; (i<splices.length) && (s=splices[i]); i++) {
for (let j=s.index; j < s.index + s.addedCount; j++) {
info.addedNodes.push(newNodes[j]);
}
}
// update cache
this._effectiveNodes = newNodes;
if (info.addedNodes.length || info.removedNodes.length) {
this.callback.call(this._target, info);
}
}
_listenSlots(nodeList) {
for (let i=0; i < nodeList.length; i++) {
let n = nodeList[i];
if (isSlot(n)) {
n.addEventListener('slotchange', this._boundSchedule);
}
}
}
_unlistenSlots(nodeList) {
for (let i=0; i < nodeList.length; i++) {
let n = nodeList[i];
if (isSlot(n)) {
n.removeEventListener('slotchange', this._boundSchedule);
}
}
}
}
Polymer.FlattenedNodesObserver = FlattenedNodesObserver;
})();
</script>

View File

@ -32,12 +32,12 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
}
function flushQueue(queue) {
for (let i=0, q, context, callback, args; i<queue.length; i++) {
while (queue.length) {
const q = queue.shift();
const context = q[0];
const callback = q[1];
const args = q[2];
try {
q = queue[i];
context = q[0];
callback = q[1];
args = q[2];
callback.apply(context, args);
} catch(e) {
setTimeout(() => {
@ -45,7 +45,13 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
})
}
}
queue.length = 0;
}
function flush() {
while (beforeRenderQueue.length || afterRenderQueue.length) {
flushQueue(beforeRenderQueue);
flushQueue(afterRenderQueue);
}
}
Polymer.RenderStatus = {
@ -62,7 +68,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
schedule();
}
afterRenderQueue.push([context, callback, args]);
}
},
flush: flush
};

View File

@ -60,6 +60,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
'unit/array-selector.html',
'unit/polymer-dom.html',
'unit/polymer-dom-observeNodes.html',
'unit/flattened-nodes-observer.html',
'unit/importHref.html',
'unit/dynamic-import.html',
'unit/gestures.html',

View File

@ -345,7 +345,7 @@ window.data = [
<dom-module id="x-simple-repeat">
<template>
<template is="dom-repeat" items="{{items}}">
<template id="repeat" is="dom-repeat" items="{{items}}">
<x-foo itema-prop="{{item.prop}}"></x-foo>
</template>
</template>

View File

@ -5653,39 +5653,36 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
});
});
test('css scoping retained when re-ordering', function(done) {
var removed;
if (!Polymer.Settings.useShadow) {
// Confirm initial scoping
var stamped = simple.root.querySelectorAll('*:not(template):not(dom-repeat)');
assert.equal(stamped[0].itemaProp, 'prop-1');
assert(stamped[0].classList.contains('x-simple-repeat'), 'expected scoping');
assert.equal(stamped.length, 3, 'total stamped count incorrect');
var row = stamped[0];
// Move
removed = simple.splice('items', 0, 1);
simple.splice('items', 1, 0, removed[0]);
setTimeout(function() {
stamped = simple.root.querySelectorAll('*:not(template):not(dom-repeat)');
assert.equal(stamped.length, 3, 'total stamped count incorrect');
assert.equal(row, stamped[1]);
assert.equal(stamped[1].itemaProp, 'prop-1');
assert(stamped[1].classList.contains('x-simple-repeat'), 'expected scoping');
// Revert
removed = simple.splice('items', 1, 1);
simple.splice('items', 0, 0, removed[0]);
setTimeout(function() {
stamped = simple.root.querySelectorAll('*:not(template):not(dom-repeat)');
assert.equal(stamped.length, 3, 'total stamped count incorrect');
assert.equal(row, stamped[0]);
assert.equal(stamped[0].itemaProp, 'prop-1');
assert(stamped[0].classList.contains('x-simple-repeat'), 'expected scoping');
done();
});
});
} else {
done();
test('css scoping retained when re-ordering', function() {
if (!window.ShadyDOM) {
this.skip();
}
var removed;
// Confirm initial scoping
var stamped = simple.root.querySelectorAll('*:not(template):not(dom-repeat)');
assert.equal(stamped[0].itemaProp, 'prop-1');
assert(stamped[0].classList.contains('x-simple-repeat'), 'expected scoping');
assert.equal(stamped.length, 3, 'total stamped count incorrect');
// Move
removed = simple.splice('items', 0, 1);
simple.splice('items', 1, 0, removed[0]);
Polymer.flush();
stamped = simple.root.querySelectorAll('*:not(template):not(dom-repeat)');
assert.equal(stamped.length, 3, 'total stamped count incorrect');
let movedItem = simple.$.repeat.modelForElement(stamped[1]).item;
assert.equal(removed[0], movedItem);
assert.equal(stamped[1].itemaProp, 'prop-1');
assert(stamped[1].classList.contains('x-simple-repeat'), 'expected scoping');
// Revert
removed = simple.splice('items', 1, 1);
simple.splice('items', 0, 0, removed[0]);
Polymer.flush();
stamped = simple.root.querySelectorAll('*:not(template):not(dom-repeat)');
assert.equal(stamped.length, 3, 'total stamped count incorrect');
movedItem = simple.$.repeat.modelForElement(stamped[0]).item;
assert.equal(removed[0], movedItem);
assert.equal(stamped[0].itemaProp, 'prop-1');
assert(stamped[0].classList.contains('x-simple-repeat'), 'expected scoping');
});
});

File diff suppressed because it is too large Load Diff

View File

@ -331,6 +331,18 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
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(Polymer.Settings.useShadow, !(window.ShadyDOM));
assert.equal(Polymer.Settings.useNativeCustomElements, !(window.customElements.polyfillWrapFlushCallback));
assert.equal(Polymer.Settings.useNativeCSSProperties, Boolean(!window.ShadyCSS || window.ShadyCSS.nativeCss));
});
})
</script>

View File

@ -73,6 +73,22 @@ suite('render-status', function() {
document.body.appendChild(el);
});
test('flush', function() {
let el = document.createElement('x-foo');
let beforeCalled;
el.beforeNextRender = function() {
beforeCalled = true;
}
let afterCalled;
el.afterNextRender = function() {
afterCalled = true;
}
document.body.appendChild(el);
Polymer.RenderStatus.flush();
assert.isTrue(beforeCalled);
assert.isTrue(afterCalled);
});
});
</script>