Skip to content
This repository has been archived by the owner on Sep 20, 2019. It is now read-only.

Adds attachShadow({shadyUpgradeFragment: documentFragment}) #316

Merged
merged 25 commits into from
Apr 13, 2019
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6261855
Adds `ShadowRoot.upgrade(fragment, host, options)`
Jan 23, 2019
71f9768
[upgrade] Fix rendering with slots
Jan 24, 2019
96be5a4
Avoid childNodes when possible.
Jan 24, 2019
3091902
More avoiding of `childNodes` when possible.
Jan 24, 2019
3da0f04
Remove superfluous check
Jan 25, 2019
eb98d72
[upgrade] Refine argument name
Jan 25, 2019
a0ce84a
Merge branch 'more-flush' into shadowRoot-upgrade
Feb 1, 2019
f542a57
Merge branch 'master' into shadowRoot-upgrade
Feb 6, 2019
a67d835
Merge branch 'master' into shadowRoot-upgrade
Feb 14, 2019
d359957
Adds `ShadyDOM.upgrade(fragment, host, options)`
Feb 14, 2019
4342887
Re-add skipped tests
Feb 14, 2019
003a148
upgarde: avoid optimal path when customElements polyfill is in use
Feb 14, 2019
2b355c2
Avoid `upgrade` on IE
Feb 15, 2019
59cff79
Address review feedback
Feb 20, 2019
87c8e35
Slight simplification based on review
Feb 20, 2019
f70e34e
Allow `ShadyDOM.attachDOM` to work in the customElements polyfill
Feb 21, 2019
ba1a238
Merge branch 'master' into shadowRoot-upgrade
Apr 1, 2019
4eb1c12
Merge branch 'wrap-className' into shadowRoot-upgrade
Apr 1, 2019
3fb6f83
Merge branch 'master' into shadowRoot-upgrade
Apr 3, 2019
9fa8f8a
Ensure scoping updates correctly when ShadyDOM.attachDom is used.
Apr 4, 2019
5d9f909
Fix event removal for platforms (e.g. old Chrome) that don't have eve…
Apr 11, 2019
9dd608d
Simplify native patching slightly and add className
Apr 11, 2019
eb6e08a
Remove `ShadyDOM.attachDom` in favor of `attachShadow({shadyUpgradeFr…
Apr 13, 2019
d585fd4
Fix typo
Apr 13, 2019
5a77147
Lint fix.
Apr 13, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 38 additions & 38 deletions src/attach-shadow.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,33 +46,38 @@ class ShadyRoot {
if (token !== ShadyRootConstructionToken) {
throw new TypeError('Illegal constructor');
}
/** @type {boolean} */
this._renderPending;
/** @type {boolean} */
this._hasRendered;
/** @type {?Array<HTMLSlotElement>} */
this._slotList = null;
/** @type {?Object<string, Array<HTMLSlotElement>>} */
this._slotMap;
/** @type {?Array<HTMLSlotElement>} */
this._pendingSlots;
this._init(this, host, options);
}

_init(root, host, options) {
// NOTE: set a fake local name so this element can be
// distinguished from a DocumentFragment when patching.
// FF doesn't allow this to be `localName`
this._localName = SHADYROOT_NAME;
root._localName = SHADYROOT_NAME;
// root <=> host
this.host = host;
root.host = host;
/** @type {!string|undefined} */
this.mode = options && options.mode;
root.mode = options && options.mode;
recordChildNodes(host);
const hostData = ensureShadyDataForNode(host);
/** @type {!ShadyRoot} */
hostData.root = this;
hostData.publicRoot = this.mode !== MODE_CLOSED ? this : null;
hostData.root = root;
hostData.publicRoot = root.mode !== MODE_CLOSED ? root : null;
// setup root
const rootData = ensureShadyDataForNode(this);
const rootData = ensureShadyDataForNode(root);
rootData.firstChild = rootData.lastChild =
rootData.parentNode = rootData.nextSibling =
rootData.previousSibling = null;
rootData.childNodes = [];
// state flags
this._renderPending = false;
this._hasRendered = false;
// marsalled lazily
this._slotList = null;
/** @type {Object<string, Array<HTMLSlotElement>>} */
this._slotMap = null;
this._pendingSlots = null;
// NOTE: optimization flag, only require an asynchronous render
// to record parsed children if flag is not set.
if (utils.settings['preferPerformance']) {
Expand All @@ -81,7 +86,7 @@ class ShadyRoot {
host[utils.NATIVE_PREFIX + 'removeChild'](n);
}
} else {
this._asyncRender();
root._asyncRender();
}
}

Expand Down Expand Up @@ -149,13 +154,11 @@ class ShadyRoot {
// if optimization flag is not set.
// on initial render remove any undistributed children.
if (!utils.settings['preferPerformance'] && !this._hasRendered) {
const c$ = this.host[utils.SHADY_PREFIX + 'childNodes'];
for (let i=0, l=c$.length; i < l; i++) {
const child = c$[i];
const data = shadyDataForNode(child);
if (child[utils.NATIVE_PREFIX + 'parentNode'] === this.host &&
(child.localName === 'slot' || !data.assignedSlot)) {
this.host[utils.NATIVE_PREFIX + 'removeChild'](child);
for (let n=this.host[utils.SHADY_PREFIX + 'firstChild']; n; n = n[utils.SHADY_PREFIX + 'nextSibling']) {
const data = shadyDataForNode(n);
if (n[utils.NATIVE_PREFIX + 'parentNode'] === this.host &&
(n.localName === 'slot' || !data.assignedSlot)) {
this.host[utils.NATIVE_PREFIX + 'removeChild'](n);
}
}
}
Expand Down Expand Up @@ -335,20 +338,18 @@ class ShadyRoot {
// Returns the list of nodes which should be rendered inside `node`.
_composeNode(node) {
let children = [];
let c$ = node[utils.SHADY_PREFIX + 'childNodes'];
for (let i = 0; i < c$.length; i++) {
let child = c$[i];
for (let n=node[utils.SHADY_PREFIX + 'firstChild']; n; n = n[utils.SHADY_PREFIX + 'nextSibling']) {
// Note: if we see a slot here, the nodes are guaranteed to need to be
// composed here. This is because if there is redistribution, it has
// already been handled by this point.
if (this._isInsertionPoint(child)) {
let flattenedNodes = shadyDataForNode(child).flattenedNodes;
if (this._isInsertionPoint(n)) {
let flattenedNodes = shadyDataForNode(n).flattenedNodes;
for (let j = 0; j < flattenedNodes.length; j++) {
let distributedNode = flattenedNodes[j];
children.push(distributedNode);
}
} else {
children.push(child);
children.push(n);
}
}
return children;
Expand All @@ -360,7 +361,7 @@ class ShadyRoot {

// Ensures that the rendered node list inside `container` is `children`.
_updateChildNodes(container, children) {
let composed = Array.prototype.slice.call(container[utils.NATIVE_PREFIX + 'childNodes']);
let composed = utils.nativeChildNodesArray(container);
let splices = calculateSplices(children, composed);
// process removals
for (let i=0, d=0, s; (i<splices.length) && (s=splices[i]); i++) {
Expand Down Expand Up @@ -462,7 +463,7 @@ class ShadyRoot {
let nA = listA[i];
let nB = listB[i];
if (nA !== nB) {
let c$ = Array.from(nA[utils.SHADY_PREFIX + 'parentNode'][utils.SHADY_PREFIX + 'childNodes']);
let c$ = utils.childNodesArray(nA[utils.SHADY_PREFIX + 'parentNode']);
return c$.indexOf(nA) - c$.indexOf(nB);
}
}
Expand Down Expand Up @@ -585,9 +586,9 @@ if (window['customElements'] && utils.settings.inUse && !utils.settings['preferP
for (let i=0; i < r.length; i++) {
const e = r[i][0], value = r[i][1];
if (value) {
e.__shadydom_connectedCallback();
e['__shadydom_connectedCallback']();
} else {
e.__shadydom_disconnectedCallback();
e['__shadydom_disconnectedCallback']();
}
}
}
Expand All @@ -612,7 +613,7 @@ if (window['customElements'] && utils.settings.inUse && !utils.settings['preferP
if (connected || disconnected) {

/** @this {!HTMLElement} */
base.prototype.connectedCallback = base.prototype.__shadydom_connectedCallback = function() {
base.prototype.connectedCallback = base.prototype['__shadydom_connectedCallback'] = function() {
// if rendering defer connected
// otherwise connect only if we haven't already
if (isRendering) {
Expand All @@ -626,7 +627,7 @@ if (window['customElements'] && utils.settings.inUse && !utils.settings['preferP
}

/** @this {!HTMLElement} */
base.prototype.disconnectedCallback = base.prototype.__shadydom_disconnectedCallback = function() {
base.prototype.disconnectedCallback = base.prototype['__shadydom_disconnectedCallback'] = function() {
// if rendering, cancel a pending connection and queue disconnect,
// otherwise disconnect only if a connection has been allowed
if (isRendering) {
Expand Down Expand Up @@ -654,8 +655,8 @@ if (window['customElements'] && utils.settings.inUse && !utils.settings['preferP
// NOTE: Instead of patching customElements.define,
// re-define on the CustomElementRegistry.prototype.define
// for Safari 10 compatibility (it's flakey otherwise).
Object.defineProperty(window['CustomElementRegistry'].prototype, 'define', {
value: function(name, constructor) {
//Object.defineProperty(window['CustomElementRegistry'].prototype, 'define', {
window.customElements.define = function(name, constructor) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're keeping this change, let's add a comment for why... did something else patch the instance rather than the prototype?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, ShadyCSS patches this directly. Changing is path of least resistance.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "fast" (tearoff) native shim also patches the instance. So nevermind, let's just align on instance patching for now.

const connected = constructor.prototype.connectedCallback;
const disconnected = constructor.prototype.disconnectedCallback;
define.call(window['customElements'], name,
Expand All @@ -666,7 +667,6 @@ if (window['customElements'] && utils.settings.inUse && !utils.settings['preferP
constructor.prototype.connectedCallback = connected;
constructor.prototype.disconnectedCallback = disconnected;
}
});

}

Expand Down
51 changes: 24 additions & 27 deletions src/link-nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ import * as utils from './utils.js';
import {shadyDataForNode, ensureShadyDataForNode} from './shady-data.js';
import {patchInsideElementAccessors, patchOutsideElementAccessors} from './patch-instances.js';

function linkNode(node, container, ref_node) {
function linkNode(node, container, containerData, ref_node) {
patchOutsideElementAccessors(node);
ref_node = ref_node || null;
const nodeData = ensureShadyDataForNode(node);
const containerData = ensureShadyDataForNode(container);
const ref_nodeData = ref_node ? ensureShadyDataForNode(ref_node) : null;
// update ref_node.previousSibling <-> node
nodeData.previousSibling = ref_node ? ref_nodeData.previousSibling :
Expand Down Expand Up @@ -54,17 +53,12 @@ export const recordInsertBefore = (node, container, ref_node) => {
}
// handle document fragments
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
let c$ = node[utils.SHADY_PREFIX + 'childNodes'];
for (let i=0; i < c$.length; i++) {
linkNode(c$[i], container, ref_node);
const first = node[utils.NATIVE_PREFIX + 'firstChild'] || null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is || null required? Native should always return null, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, removing.

for (let n = first; n; (n = n[utils.NATIVE_PREFIX + 'nextSibling'])) {
linkNode(n, container, containerData, ref_node);
}
// cleanup logical dom in doc fragment.
const nodeData = ensureShadyDataForNode(node);
let resetTo = (nodeData.firstChild !== undefined) ? null : undefined;
nodeData.firstChild = nodeData.lastChild = resetTo;
nodeData.childNodes = resetTo;
kevinpschaaf marked this conversation as resolved.
Show resolved Hide resolved
} else {
linkNode(node, container, ref_node);
linkNode(node, container, containerData, ref_node);
}
}

Expand Down Expand Up @@ -97,23 +91,26 @@ export const recordRemoveChild = (node, container) => {
}

/**
* @param {!Node} node
* @param {!Node|DocumentFragment} node
* @param {!Node|DocumentFragment=} root
*/
export const recordChildNodes = (node) => {
export const recordChildNodes = (node, root) => {
const nodeData = ensureShadyDataForNode(node);
if (nodeData.firstChild === undefined) {
// remove caching of childNodes
nodeData.childNodes = null;
const first = nodeData.firstChild = node[utils.NATIVE_PREFIX + 'firstChild'] || null;
nodeData.lastChild = node[utils.NATIVE_PREFIX + 'lastChild'] || null;
patchInsideElementAccessors(node);
for (let n = first, previous; n; (n = n[utils.NATIVE_PREFIX + 'nextSibling'])) {
const sd = ensureShadyDataForNode(n);
sd.parentNode = node;
sd.nextSibling = n[utils.NATIVE_PREFIX + 'nextSibling'] || null;
sd.previousSibling = previous || null;
previous = n;
patchOutsideElementAccessors(n);
}
if (!root && nodeData.firstChild !== undefined) {
return;
}
root = root || node;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confusing to call this root here, it's really parent. L110 could just be sd.parentNode = root || node.

It might be good to call the root argument adoptedRoot or something like that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to adoptedParent

// remove caching of childNodes
nodeData.childNodes = null;
const first = nodeData.firstChild = node[utils.NATIVE_PREFIX + 'firstChild'] || null;
nodeData.lastChild = node[utils.NATIVE_PREFIX + 'lastChild'] || null;
patchInsideElementAccessors(node);
for (let n = first, previous; n; (n = n[utils.NATIVE_PREFIX + 'nextSibling'])) {
const sd = ensureShadyDataForNode(n);
sd.parentNode = root;
sd.nextSibling = n[utils.NATIVE_PREFIX + 'nextSibling'] || null;
sd.previousSibling = previous || null;
previous = n;
patchOutsideElementAccessors(n);
}
}
2 changes: 1 addition & 1 deletion src/patch-native.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ export const addNativePrefixedProperties = () => {
innerHTML: {
/** @this {Element} */
get() {
return getInnerHTML(this, n => n[NATIVE_PREFIX + 'childNodes']);
return getInnerHTML(this, utils.nativeChildNodesArray);
},
// Needed on browsers that do not proper accessors (e.g. old versions of Chrome)
/** @this {Element} */
Expand Down
5 changes: 2 additions & 3 deletions src/patches/Document.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ export const DocumentPatches = utils.getOwnPropertyDescriptors({
}
let n = this[utils.NATIVE_PREFIX + 'importNode'](node, false);
if (deep) {
let c$ = node[utils.SHADY_PREFIX + 'childNodes'];
for (let i=0, nc; i < c$.length; i++) {
nc = this[utils.SHADY_PREFIX + 'importNode'](c$[i], true);
for (let c=node[utils.SHADY_PREFIX + 'firstChild'], nc; c; c = c[utils.SHADY_PREFIX + 'nextSibling']) {
nc = this[utils.SHADY_PREFIX + 'importNode'](c, true);
n[utils.SHADY_PREFIX + 'appendChild'](nc);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/patches/ElementOrShadowRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const ElementOrShadowRootPatches = utils.getOwnPropertyDescriptors({
if (utils.isTrackingLogicalChildNodes(this)) {
const content = this.localName === 'template' ?
/** @type {HTMLTemplateElement} */(this).content : this;
return getInnerHTML(content, (e) => e[utils.SHADY_PREFIX + 'childNodes']);
return getInnerHTML(content, utils.childNodesArray);
} else {
return this[utils.NATIVE_PREFIX + 'innerHTML'];
}
Expand Down
5 changes: 4 additions & 1 deletion src/patches/HTMLElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,17 @@ eventPropertyNames.forEach(property => {
set: function(fn) {
const shadyData = ensureShadyDataForNode(this);
const eventName = property.substring(2);
if (!shadyData.__onCallbackListeners) {
shadyData.__onCallbackListeners = {};
}
shadyData.__onCallbackListeners[property] && this.removeEventListener(eventName, shadyData.__onCallbackListeners[property]);
this[utils.SHADY_PREFIX + 'addEventListener'](eventName, fn);
shadyData.__onCallbackListeners[property] = fn;
},
/** @this {HTMLElement} */
get() {
const shadyData = shadyDataForNode(this);
return shadyData && shadyData.__onCallbackListeners[property];
return shadyData && shadyData.__onCallbackListeners && shadyData.__onCallbackListeners[property];
},
configurable: true
};
Expand Down
16 changes: 7 additions & 9 deletions src/patches/Node.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ export function clearNode(node) {
function removeOwnerShadyRoot(node) {
// optimization: only reset the tree if node is actually in a root
if (hasCachedOwnerRoot(node)) {
let c$ = node[utils.SHADY_PREFIX + 'childNodes'];
for (let i=0, l=c$.length, n; (i<l) && (n=c$[i]); i++) {
for (let n=node[utils.SHADY_PREFIX + 'firstChild']; n; n = n[utils.SHADY_PREFIX + 'nextSibling']) {
removeOwnerShadyRoot(n);
}
}
Expand Down Expand Up @@ -187,9 +186,9 @@ export const NodePatches = utils.getOwnPropertyDescriptors({
get textContent() {
if (utils.isTrackingLogicalChildNodes(this)) {
let tc = [];
for (let i = 0, cn = this[utils.SHADY_PREFIX + 'childNodes'], c; (c = cn[i]); i++) {
if (c.nodeType !== Node.COMMENT_NODE) {
tc.push(c[utils.SHADY_PREFIX + 'textContent']);
for (let n=this[utils.SHADY_PREFIX + 'firstChild']; n; n = n[utils.SHADY_PREFIX + 'nextSibling']) {
if (n.nodeType !== Node.COMMENT_NODE) {
tc.push(n[utils.SHADY_PREFIX + 'textContent']);
}
}
return tc.join('');
Expand Down Expand Up @@ -307,7 +306,7 @@ export const NodePatches = utils.getOwnPropertyDescriptors({
});
}
// if a slot is added, must render containing root.
if (this.localName === 'slot' || slotsAdded.length) {
if (slotsAdded.length) {
Copy link
Contributor

@kevinpschaaf kevinpschaaf Feb 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can this.localName === 'slot' be removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was incorrect. This says "the node we're inserting into is a slot" and in this case a slot was not added. What it was meant to trap was node.localName but this is not needed because the treeVisitor checks node.

if (slotsAdded.length) {
ownerRoot._addSlots(slotsAdded);
}
Expand Down Expand Up @@ -447,9 +446,8 @@ export const NodePatches = utils.getOwnPropertyDescriptors({
// been removed from the spec.
// Make sure we do not do a deep clone on them for old browsers (IE11)
if (deep && n.nodeType !== Node.ATTRIBUTE_NODE) {
let c$ = this[utils.SHADY_PREFIX + 'childNodes'];
for (let i=0, nc; i < c$.length; i++) {
nc = c$[i][utils.SHADY_PREFIX + 'cloneNode'](true);
for (let c=this[utils.SHADY_PREFIX + 'firstChild'], nc; c; c = c[utils.SHADY_PREFIX + 'nextSibling']) {
nc = c[utils.SHADY_PREFIX + 'cloneNode'](true);
n[utils.SHADY_PREFIX + 'appendChild'](nc);
}
}
Expand Down
14 changes: 7 additions & 7 deletions src/patches/ParentNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ import {shadyDataForNode} from '../shady-data.js';
*/
export function query(node, matcher, halter) {
let list = [];
queryElements(node[utils.SHADY_PREFIX + 'childNodes'], matcher,
queryChildNodes(node, matcher,
halter, list);
return list;
}

function queryElements(elements, matcher, halter, list) {
for (let i=0, l=elements.length, c; (i<l) && (c=elements[i]); i++) {
if (c.nodeType === Node.ELEMENT_NODE &&
queryElement(c, matcher, halter, list)) {
function queryChildNodes(parent, matcher, halter, list) {
for (let n = parent[utils.SHADY_PREFIX + 'firstChild']; n; n = n[utils.SHADY_PREFIX + 'nextSibling']) {
if (n.nodeType === Node.ELEMENT_NODE &&
queryElement(n, matcher, halter, list)) {
return true;
}
}
Expand All @@ -40,7 +40,7 @@ function queryElement(node, matcher, halter, list) {
if (halter && halter(result)) {
return result;
}
queryElements(node[utils.SHADY_PREFIX + 'childNodes'], matcher,
queryChildNodes(node, matcher,
halter, list);
}

Expand Down Expand Up @@ -81,7 +81,7 @@ export const ParentNodePatches = utils.getOwnPropertyDescriptors({
return this[utils.NATIVE_PREFIX + 'children'];
}
return utils.createPolyfilledHTMLCollection(Array.prototype.filter.call(
this[utils.SHADY_PREFIX + 'childNodes'], function(n) {
utils.childNodesArray(this), (n) => {
return (n.nodeType === Node.ELEMENT_NODE);
}));
},
Expand Down
Loading