@webcomponents/custom-elements
Version:
HTML Custom Elements Polyfill
236 lines (197 loc) • 7.38 kB
JavaScript
import Native from './Native.js';
import CustomElementInternals from '../CustomElementInternals.js';
import * as Utilities from '../Utilities.js';
/**
* @param {!CustomElementInternals} internals
*/
export default function(internals) {
// `Node#nodeValue` is implemented on `Attr`.
// `Node#textContent` is implemented on `Attr`, `Element`.
Utilities.setPropertyUnchecked(Node.prototype, 'insertBefore',
/**
* @this {Node}
* @param {!Node} node
* @param {?Node} refNode
* @return {!Node}
*/
function(node, refNode) {
if (node instanceof DocumentFragment) {
const insertedNodes = Array.prototype.slice.apply(node.childNodes);
const nativeResult = Native.Node_insertBefore.call(this, node, refNode);
// DocumentFragments can't be connected, so `disconnectTree` will never
// need to be called on a DocumentFragment's children after inserting it.
if (Utilities.isConnected(this)) {
for (let i = 0; i < insertedNodes.length; i++) {
internals.connectTree(insertedNodes[i]);
}
}
return nativeResult;
}
const nodeWasConnected = Utilities.isConnected(node);
const nativeResult = Native.Node_insertBefore.call(this, node, refNode);
if (nodeWasConnected) {
internals.disconnectTree(node);
}
if (Utilities.isConnected(this)) {
internals.connectTree(node);
}
return nativeResult;
});
Utilities.setPropertyUnchecked(Node.prototype, 'appendChild',
/**
* @this {Node}
* @param {!Node} node
* @return {!Node}
*/
function(node) {
if (node instanceof DocumentFragment) {
const insertedNodes = Array.prototype.slice.apply(node.childNodes);
const nativeResult = Native.Node_appendChild.call(this, node);
// DocumentFragments can't be connected, so `disconnectTree` will never
// need to be called on a DocumentFragment's children after inserting it.
if (Utilities.isConnected(this)) {
for (let i = 0; i < insertedNodes.length; i++) {
internals.connectTree(insertedNodes[i]);
}
}
return nativeResult;
}
const nodeWasConnected = Utilities.isConnected(node);
const nativeResult = Native.Node_appendChild.call(this, node);
if (nodeWasConnected) {
internals.disconnectTree(node);
}
if (Utilities.isConnected(this)) {
internals.connectTree(node);
}
return nativeResult;
});
Utilities.setPropertyUnchecked(Node.prototype, 'cloneNode',
/**
* @this {Node}
* @param {boolean=} deep
* @return {!Node}
*/
function(deep) {
const clone = Native.Node_cloneNode.call(this, deep);
// Only create custom elements if this element's owner document is
// associated with the registry.
if (!this.ownerDocument.__CE_hasRegistry) {
internals.patchTree(clone);
} else {
internals.patchAndUpgradeTree(clone);
}
return clone;
});
Utilities.setPropertyUnchecked(Node.prototype, 'removeChild',
/**
* @this {Node}
* @param {!Node} node
* @return {!Node}
*/
function(node) {
const nodeWasConnected = Utilities.isConnected(node);
const nativeResult = Native.Node_removeChild.call(this, node);
if (nodeWasConnected) {
internals.disconnectTree(node);
}
return nativeResult;
});
Utilities.setPropertyUnchecked(Node.prototype, 'replaceChild',
/**
* @this {Node}
* @param {!Node} nodeToInsert
* @param {!Node} nodeToRemove
* @return {!Node}
*/
function(nodeToInsert, nodeToRemove) {
if (nodeToInsert instanceof DocumentFragment) {
const insertedNodes = Array.prototype.slice.apply(nodeToInsert.childNodes);
const nativeResult = Native.Node_replaceChild.call(this, nodeToInsert, nodeToRemove);
// DocumentFragments can't be connected, so `disconnectTree` will never
// need to be called on a DocumentFragment's children after inserting it.
if (Utilities.isConnected(this)) {
internals.disconnectTree(nodeToRemove);
for (let i = 0; i < insertedNodes.length; i++) {
internals.connectTree(insertedNodes[i]);
}
}
return nativeResult;
}
const nodeToInsertWasConnected = Utilities.isConnected(nodeToInsert);
const nativeResult = Native.Node_replaceChild.call(this, nodeToInsert, nodeToRemove);
const thisIsConnected = Utilities.isConnected(this);
if (thisIsConnected) {
internals.disconnectTree(nodeToRemove);
}
if (nodeToInsertWasConnected) {
internals.disconnectTree(nodeToInsert);
}
if (thisIsConnected) {
internals.connectTree(nodeToInsert);
}
return nativeResult;
});
function patch_textContent(destination, baseDescriptor) {
Object.defineProperty(destination, 'textContent', {
enumerable: baseDescriptor.enumerable,
configurable: true,
get: baseDescriptor.get,
set: /** @this {Node} */ function(assignedValue) {
// If this is a text node then there are no nodes to disconnect.
if (this.nodeType === Node.TEXT_NODE) {
baseDescriptor.set.call(this, assignedValue);
return;
}
let removedNodes = undefined;
// Checking for `firstChild` is faster than reading `childNodes.length`
// to compare with 0.
if (this.firstChild) {
// Using `childNodes` is faster than `children`, even though we only
// care about elements.
const childNodes = this.childNodes;
const childNodesLength = childNodes.length;
if (childNodesLength > 0 && Utilities.isConnected(this)) {
// Copying an array by iterating is faster than using slice.
removedNodes = new Array(childNodesLength);
for (let i = 0; i < childNodesLength; i++) {
removedNodes[i] = childNodes[i];
}
}
}
baseDescriptor.set.call(this, assignedValue);
if (removedNodes) {
for (let i = 0; i < removedNodes.length; i++) {
internals.disconnectTree(removedNodes[i]);
}
}
},
});
}
if (Native.Node_textContent && Native.Node_textContent.get) {
patch_textContent(Node.prototype, Native.Node_textContent);
} else {
internals.addPatch(function(element) {
patch_textContent(element, {
enumerable: true,
configurable: true,
// NOTE: This implementation of the `textContent` getter assumes that
// text nodes' `textContent` getter will not be patched.
get: /** @this {Node} */ function() {
/** @type {!Array<string>} */
const parts = [];
for (let i = 0; i < this.childNodes.length; i++) {
parts.push(this.childNodes[i].textContent);
}
return parts.join('');
},
set: /** @this {Node} */ function(assignedValue) {
while (this.firstChild) {
Native.Node_removeChild.call(this, this.firstChild);
}
Native.Node_appendChild.call(this, document.createTextNode(assignedValue));
},
});
});
}
};