diff --git a/externs/custom-elements.js b/externs/custom-elements.js index 496c0ee..d35a24e 100644 --- a/externs/custom-elements.js +++ b/externs/custom-elements.js @@ -60,5 +60,5 @@ Element.prototype.__CE_state; /** @type {!CustomElementDefinition|undefined} */ Element.prototype.__CE_definition; -/** @type {!DocumentFragment|undefined} */ +/** @type {!ShadowRoot|null|undefined} */ Element.prototype.__CE_shadowRoot; diff --git a/src/CustomElementInternals.js b/src/CustomElementInternals.js index b39c4cd..6747e78 100644 --- a/src/CustomElementInternals.js +++ b/src/CustomElementInternals.js @@ -8,6 +8,12 @@ * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ +import {constructor as DocumentCtor, proxy as DocumentProxy} from './Environment/Document.js'; +import {proxy as ElementProxy} from './Environment/Element.js'; +import {proxy as EventTargetProxy} from './Environment/EventTarget.js'; +import {proxy as HTMLLinkElementProxy} from './Environment/HTMLLinkElement.js'; +import {constructor as NodeCtor, proxy as NodeProxy} from './Environment/Node.js'; + import * as Utilities from './Utilities.js'; import CEState from './CustomElementState.js'; @@ -218,10 +224,16 @@ export default class CustomElementInternals { const elements = []; const gatherElements = element => { - if (element.localName === 'link' && element.getAttribute('rel') === 'import') { + if (ElementProxy.localName(element) === 'link' && + ElementProxy.getAttribute(element, 'rel') === 'import') { // The HTML Imports polyfill sets a descendant element of the link to // the `import` property, specifically this is *not* a Document. - const importNode = /** @type {?Node} */ (element.import); + const importNode = /** @type {?Node} */ (HTMLLinkElementProxy.import(element)); + const readyState = importNode instanceof DocumentCtor + // Native HTML Imports. + ? DocumentProxy.readyState(/** @type {!Document} */ (importNode)) + // HTML Imports polyfill. + : (importNode instanceof NodeCtor ? importNode.readyState : undefined); if (importNode instanceof Node) { importNode.__CE_isImportDocument = true; @@ -234,7 +246,7 @@ export default class CustomElementInternals { } else { // If this link's import root is not available, its contents can't be // walked. Wait for 'load' and walk it when it's ready. - element.addEventListener('load', () => { + EventTargetProxy.addEventListener(element, 'load', () => { const importNode = /** @type {!Node} */ (element.import); if (importNode.__CE_documentLoadHandled) return; @@ -288,13 +300,15 @@ export default class CustomElementInternals { // "The defaultView IDL attribute of the Document interface, on getting, // must return this Document's browsing context's WindowProxy object, if // this Document has an associated browsing context, or null otherwise." - const ownerDocument = element.ownerDocument; + const ownerDocument = /** @type {!Document} */ ( + NodeProxy.ownerDocument(element)); if ( - !ownerDocument.defaultView && + !DocumentProxy.defaultView(ownerDocument) && !(ownerDocument.__CE_isImportDocument && ownerDocument.__CE_hasRegistry) ) return; - const definition = this.localNameToDefinition(element.localName); + const localName = ElementProxy.localName(element); + const definition = this.localNameToDefinition(localName); if (!definition) return; definition.constructionStack.push(element); @@ -322,7 +336,7 @@ export default class CustomElementInternals { const observedAttributes = definition.observedAttributes; for (let i = 0; i < observedAttributes.length; i++) { const name = observedAttributes[i]; - const value = element.getAttribute(name); + const value = ElementProxy.getAttribute(element, name); if (value !== null) { this.attributeChangedCallback(element, name, null, value, null); } diff --git a/src/CustomElementRegistry.js b/src/CustomElementRegistry.js index 4e919dc..8818eb0 100644 --- a/src/CustomElementRegistry.js +++ b/src/CustomElementRegistry.js @@ -12,6 +12,7 @@ import CustomElementInternals from './CustomElementInternals.js'; import DocumentConstructionObserver from './DocumentConstructionObserver.js'; import Deferred from './Deferred.js'; import * as Utilities from './Utilities.js'; +import { proxy as ElementProxy } from './Environment/Element.js'; /** * @unrestricted @@ -176,7 +177,7 @@ export default class CustomElementRegistry { // Ignore the element if it has already upgraded or failed to upgrade. if (element.__CE_state !== undefined) return; - const localName = element.localName; + const localName = ElementProxy.localName(element); // If there is an applicable pending definition for the element, add the // element to the list of elements to be upgraded with that definition. diff --git a/src/DocumentConstructionObserver.js b/src/DocumentConstructionObserver.js index b079e3f..50afa2d 100644 --- a/src/DocumentConstructionObserver.js +++ b/src/DocumentConstructionObserver.js @@ -8,6 +8,10 @@ * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ +import {proxy as DocumentProxy} from './Environment/Document.js'; +import {constructor as MutationObserverCtor, proxy as MutationObserverProxy} from './Environment/MutationObserver.js'; +import {proxy as MutationRecordProxy} from './Environment/MutationRecord.js'; + import CustomElementInternals from './CustomElementInternals.js'; export default class DocumentConstructionObserver { @@ -32,14 +36,14 @@ export default class DocumentConstructionObserver { // document. this._internals.patchAndUpgradeTree(this._document); - if (this._document.readyState === 'loading') { - this._observer = new MutationObserver(this._handleMutations.bind(this)); + if (DocumentProxy.readyState(this._document) === 'loading') { + this._observer = new MutationObserverCtor(this._handleMutations.bind(this)); // Nodes created by the parser are given to the observer *before* the next // task runs. Inline scripts are run in a new task. This means that the // observer will be able to handle the newly parsed nodes before the inline // script is run. - this._observer.observe(this._document, { + MutationObserverProxy.observe(this._observer, this._document, { childList: true, subtree: true, }); @@ -48,7 +52,7 @@ export default class DocumentConstructionObserver { disconnect() { if (this._observer) { - this._observer.disconnect(); + MutationObserverProxy.disconnect(this._observer); } } @@ -59,13 +63,13 @@ export default class DocumentConstructionObserver { // Once the document's `readyState` is 'interactive' or 'complete', all new // nodes created within that document will be the result of script and // should be handled by patching. - const readyState = this._document.readyState; + const readyState = DocumentProxy.readyState(this._document); if (readyState === 'interactive' || readyState === 'complete') { this.disconnect(); } for (let i = 0; i < mutations.length; i++) { - const addedNodes = mutations[i].addedNodes; + const addedNodes = MutationRecordProxy.addedNodes(mutations[i]); for (let j = 0; j < addedNodes.length; j++) { const node = addedNodes[j]; this._internals.patchAndUpgradeTree(node); diff --git a/src/Environment/Document.js b/src/Environment/Document.js new file mode 100644 index 0000000..99af7ae --- /dev/null +++ b/src/Environment/Document.js @@ -0,0 +1,58 @@ +/** + * @license + * Copyright (c) 2016 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 {getDescriptor, getter, method} from "./Utilities.js"; + +export const constructor = window['Document']; +export const proto = constructor['prototype']; + +export const descriptors = { + append: getDescriptor(proto, 'append'), + createElement: getDescriptor(proto, 'createElement'), + createElementNS: getDescriptor(proto, 'createElementNS'), + createTextNode: getDescriptor(proto, 'createTextNode'), + documentElement: getDescriptor(proto, 'documentElement'), + importNode: getDescriptor(proto, 'importNode'), + prepend: getDescriptor(proto, 'prepend'), + readyState: getDescriptor(proto, 'readyState'), + defaultView: getDescriptor(proto, 'defaultView'), +}; + +/** @type {function(this: Document, !string): !HTMLElement} */ +const createElementMethod = method(descriptors.createElement); +/** @type {function(this: Document, ?string, !string): !Element} */ +const createElementNSMethod = method(descriptors.createElementNS); +/** @type {function(this: Document, !string): !Text} */ +const createTextNodeMethod = method(descriptors.createTextNode); +/** @type {function(this: Document): ?Element} */ +const documentElementGetter = getter(descriptors.documentElement, function() { return this.documentElement; }); +/** @type {function(this: Document, !Node, boolean=): !Node} */ +const importNodeMethod = method(descriptors.importNode); +/** @type {function(this: Document): (!string|undefined)} */ +const readyStateGetter = getter(descriptors.readyState, function() { return this.readyState; }); +/** @type {function(this: Document): ?Window} */ +const defaultViewGetter = getter(descriptors.defaultView, function() { return this.defaultView; }); + +export const proxy = { + /** @type {function(!Document, !string): !HTMLElement} */ + createElement: (doc, localName) => createElementMethod.call(doc, localName), + /** @type {function(!Document, ?string, !string): !Element} */ + createElementNS: (doc, namespace, qualifiedName) => createElementNSMethod.call(doc, namespace, qualifiedName), + /** @type {function(!Document, !string): !Text} */ + createTextNode: (doc, localName) => createTextNodeMethod.call(doc, localName), + /** @type {function(!Document): ?Element} */ + documentElement: (doc) => documentElementGetter.call(doc), + /** @type {function(!Document, !Node, boolean=): !Node} */ + importNode: (doc, node, deep) => importNodeMethod.call(doc, node, deep), + /** @type {function(!Document): (!string|undefined)} */ + readyState: doc => readyStateGetter.call(doc), + /** @type {function(!Document): ?Window} */ + defaultView: doc => defaultViewGetter.call(doc), +}; diff --git a/src/Environment/DocumentFragment.js b/src/Environment/DocumentFragment.js new file mode 100644 index 0000000..b4de61f --- /dev/null +++ b/src/Environment/DocumentFragment.js @@ -0,0 +1,31 @@ +/** + * @license + * Copyright (c) 2016 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 {getDescriptor, getter, method} from "./Utilities.js"; + +export const constructor = window['DocumentFragment']; +export const proto = constructor['prototype']; + +export const descriptors = { + append: getDescriptor(proto, 'append'), + prepend: getDescriptor(proto, 'prepend'), +}; + +/** @type {function(this: DocumentFragment, ...(!Node|!string))} */ +const appendMethod = method(descriptors.append); +/** @type {function(this: DocumentFragment, ...(!Node|!string))} */ +const prependMethod = method(descriptors.prepend); + +export const proxy = { + /** @type {function(!DocumentFragment, ...(!Node|!string))} */ + append: (fragment, ...nodes) => appendMethod.call(fragment, ...nodes), + /** @type {function(!DocumentFragment, ...(!Node|!string))} */ + prepend: (fragment, ...nodes) => prependMethod.call(fragment, ...nodes), +}; diff --git a/src/Environment/Element.js b/src/Environment/Element.js new file mode 100644 index 0000000..be26761 --- /dev/null +++ b/src/Environment/Element.js @@ -0,0 +1,78 @@ +/** + * @license + * Copyright (c) 2016 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 {getDescriptor, getter, method} from "./Utilities.js"; + +export const constructor = window['Element']; +export const proto = constructor['prototype']; + +export const descriptors = { + after: getDescriptor(proto, 'after'), + append: getDescriptor(proto, 'append'), + attachShadow: getDescriptor(proto, 'attachShadow'), + before: getDescriptor(proto, 'before'), + getAttribute: getDescriptor(proto, 'getAttribute'), + getAttributeNS: getDescriptor(proto, 'getAttributeNS'), + innerHTML: getDescriptor(proto, 'innerHTML'), + insertAdjacentElement: getDescriptor(proto, 'insertAdjacentElement'), + insertAdjacentHTML: getDescriptor(proto, 'insertAdjacentHTML'), + localName: getDescriptor(proto, 'localName'), + namespaceURI: getDescriptor(proto, 'namespaceURI'), + prepend: getDescriptor(proto, 'prepend'), + remove: getDescriptor(proto, 'remove'), + removeAttribute: getDescriptor(proto, 'removeAttribute'), + removeAttributeNS: getDescriptor(proto, 'removeAttributeNS'), + replaceWith: getDescriptor(proto, 'replaceWith'), + setAttribute: getDescriptor(proto, 'setAttribute'), + setAttributeNS: getDescriptor(proto, 'setAttributeNS'), +}; + +/** @type {function(this: Element, !{mode: !string}): ShadowRoot} */ +const attachShadowMethod = method(descriptors.attachShadow); +/** @type {function(this: Element, !string): ?string} */ +const getAttributeMethod = method(descriptors.getAttribute); +/** @type {function(this: Element, ?string, !string): ?string} */ +const getAttributeNSMethod = method(descriptors.getAttributeNS); +/** @type {function(this: Element): !string} */ +const localNameGetter = getter( + descriptors.localName || /* Edge / IE11 */ getDescriptor(window['Node'].prototype, 'localName'), + function() { return this.localName; }); +/** @type {function(this: Element): !string} */ +const namespaceURIGetter = getter(descriptors.namespaceURI, + function() { return this.namespaceURI; }); +/** @type {function(this: Element, !string)} */ +const removeAttributeMethod = method(descriptors.removeAttribute); +/** @type {function(this: Element, ?string, !string)} */ +const removeAttributeNSMethod = method(descriptors.removeAttributeNS); +/** @type {function(this: Element, !string, !string): ?string} */ +const setAttributeMethod = method(descriptors.setAttribute); +/** @type {function(this: Element, ?string, !string, !string): ?string} */ +const setAttributeNSMethod = method(descriptors.setAttributeNS); + +export const proxy = { + /** @type {function(!Element, !{mode: !string}): ShadowRoot} */ + attachShadow: (node, options) => attachShadowMethod.call(node, options), + /** @type {function(!Element, !string): ?string} */ + getAttribute: (node, name) => getAttributeMethod.call(node, name), + /** @type {function(!Element, ?string, !string): ?string} */ + getAttributeNS: (node, ns, name) => getAttributeNSMethod.call(node, ns, name), + /** @type {function(!Element): !string} */ + localName: node => localNameGetter.call(node), + /** @type {function(!Element): !string} */ + namespaceURI: node => namespaceURIGetter.call(node), + /** @type {function(!Element, !string)} */ + removeAttribute: (node, name) => removeAttributeMethod.call(node, name), + /** @type {function(!Element, ?string, !string)} */ + removeAttributeNS: (node, ns, name) => removeAttributeNSMethod.call(node, ns, name), + /** @type {function(!Element, !string, !string): ?string} */ + setAttribute: (node, name, value) => setAttributeMethod.call(node, name, value), + /** @type {function(!Element, ?string, !string, !string): ?string} */ + setAttributeNS: (node, ns, name, value) => setAttributeNSMethod.call(node, ns, name, value), +}; diff --git a/src/Environment/EventTarget.js b/src/Environment/EventTarget.js new file mode 100644 index 0000000..3ad6881 --- /dev/null +++ b/src/Environment/EventTarget.js @@ -0,0 +1,30 @@ +/** + * @license + * Copyright (c) 2016 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 {getDescriptor, getter, method} from "./Utilities.js"; + +export const constructor = window['EventTarget']; +export const proto = constructor ? constructor['prototype'] : undefined; + +export const descriptors = !proto ? {} : { + addEventListener: getDescriptor(proto, 'addEventListener'), +}; + +/** @type {function(this: EventTarget, !string, ?Function, AddEventListenerOptions=)} */ +const addEventListenerMethod = + method(descriptors.addEventListener) || + // IE11 + method(getDescriptor(window['Node']['prototype'], 'addEventListener')); + +export const proxy = { + /** @type {function(!EventTarget, !string, ?Function, AddEventListenerOptions=)} */ + addEventListener: (node, type, callback, options) => + addEventListenerMethod.call(node, type, callback, options), +}; diff --git a/src/Environment/HTMLElement.js b/src/Environment/HTMLElement.js new file mode 100644 index 0000000..cc4aaf9 --- /dev/null +++ b/src/Environment/HTMLElement.js @@ -0,0 +1,29 @@ +/** + * @license + * Copyright (c) 2016 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 {getDescriptor, getter, method} from "./Utilities.js"; + +export const constructor = window['HTMLElement']; +export const proto = constructor['prototype']; + +export const descriptors = { + contains: getDescriptor(proto, 'contains'), + innerHTML: getDescriptor(proto, 'innerHTML'), + insertAdjacentElement: getDescriptor(proto, 'insertAdjacentElement'), + insertAdjacentHTML: getDescriptor(proto, 'insertAdjacentHTML'), +}; + +/** @type {function(this: Node, ?Node): boolean} */ +const containsMethod = method(descriptors.contains); + +export const proxy = { + /** @type {function(!Node, ?Node): boolean} */ + contains: (node, other) => containsMethod.call(node, other), +}; diff --git a/src/Environment/HTMLLinkElement.js b/src/Environment/HTMLLinkElement.js new file mode 100644 index 0000000..90aea72 --- /dev/null +++ b/src/Environment/HTMLLinkElement.js @@ -0,0 +1,26 @@ +/** + * @license + * Copyright (c) 2016 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 {getDescriptor, getter, method} from "./Utilities.js"; + +export const constructor = window['HTMLLinkElement']; +export const proto = constructor ? constructor['prototype'] : undefined; + +export const descriptors = { + import: getDescriptor(proto, 'import'), +}; + +/** @type {function(this: HTMLLinkElement): ?Document} */ +const importGetter = getter(descriptors.import, function() { return this.import; }); + +export const proxy = { + /** @type {function(!HTMLLinkElement): ?Document} */ + import: node => importGetter.call(node), +}; diff --git a/src/Environment/HTMLTemplateElement.js b/src/Environment/HTMLTemplateElement.js new file mode 100644 index 0000000..1b6541f --- /dev/null +++ b/src/Environment/HTMLTemplateElement.js @@ -0,0 +1,26 @@ +/** + * @license + * Copyright (c) 2016 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 {getDescriptor, getter, method} from "./Utilities.js"; + +export const constructor = window['HTMLTemplateElement']; +export const proto = constructor ? constructor['prototype'] : undefined; + +export const descriptors = !proto ? {} : { + content: getDescriptor(proto, 'content'), +}; + +/** @type {function(this: HTMLTemplateElement): !DocumentFragment} */ +const contentGetter = getter(descriptors.content, function() { return this.content; }); + +export const proxy = { + /** @type {function(!HTMLTemplateElement): !DocumentFragment} */ + content: node => contentGetter.call(node), +}; diff --git a/src/Environment/MutationObserver.js b/src/Environment/MutationObserver.js new file mode 100644 index 0000000..418b91f --- /dev/null +++ b/src/Environment/MutationObserver.js @@ -0,0 +1,32 @@ +/** + * @license + * Copyright (c) 2016 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 {getDescriptor, getter, method} from "./Utilities.js"; + +/** @type {function(new: MutationObserver, function(...?): ?)} */ +export const constructor = window['MutationObserver']; +export const proto = constructor['prototype']; + +export const descriptors = { + disconnect: getDescriptor(proto, 'disconnect'), + observe: getDescriptor(proto, 'observe'), +}; + +/** @type {function(this: MutationObserver)} */ +const disconnectMethod = method(descriptors.disconnect); +/** @type {function(this: MutationObserver, !Node, !MutationObserverInit=)} */ +const observeMethod = method(descriptors.observe); + +export const proxy = { + /** @type {function(!MutationObserver)} */ + disconnect: mutationObserver => disconnectMethod.call(mutationObserver), + /** @type {function(!MutationObserver, !Node, !MutationObserverInit=)} */ + observe: (mutationObserver, target, options) => observeMethod.call(mutationObserver, target, options), +}; diff --git a/src/Environment/MutationRecord.js b/src/Environment/MutationRecord.js new file mode 100644 index 0000000..155a310 --- /dev/null +++ b/src/Environment/MutationRecord.js @@ -0,0 +1,26 @@ +/** + * @license + * Copyright (c) 2016 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 {getDescriptor, getter, method} from "./Utilities.js"; + +export const constructor = window['MutationRecord']; +export const proto = constructor['prototype']; + +export const descriptors = { + addedNodes: getDescriptor(proto, 'addedNodes'), +}; + +/** @type {function(this: MutationRecord): (!NodeList|undefined)} */ +const addedNodesGetter = getter(descriptors.addedNodes, function() { return this.addedNodes; }); + +export const proxy = { + /** @type {function(!MutationRecord): (!NodeList|undefined)} */ + addedNodes: node => addedNodesGetter.call(node), +}; diff --git a/src/Environment/Node.js b/src/Environment/Node.js new file mode 100644 index 0000000..6d5ee41 --- /dev/null +++ b/src/Environment/Node.js @@ -0,0 +1,91 @@ +/** + * @license + * Copyright (c) 2016 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 {getDescriptor, getter, method} from "./Utilities.js"; + +export const constructor = window['Node']; +export const proto = constructor['prototype']; + +export const descriptors = { + appendChild: getDescriptor(proto, 'appendChild'), + childNodes: getDescriptor(proto, 'childNodes'), + cloneNode: getDescriptor(proto, 'cloneNode'), + contains: getDescriptor(proto, 'contains'), + firstChild: getDescriptor(proto, 'firstChild'), + insertBefore: getDescriptor(proto, 'insertBefore'), + isConnected: getDescriptor(proto, 'isConnected'), + nextSibling: getDescriptor(proto, 'nextSibling'), + nodeType: getDescriptor(proto, 'nodeType'), + ownerDocument: getDescriptor(proto, 'ownerDocument'), + parentNode: getDescriptor(proto, 'parentNode'), + removeChild: getDescriptor(proto, 'removeChild'), + replaceChild: getDescriptor(proto, 'replaceChild'), + textContent: getDescriptor(proto, 'textContent'), +}; + +/** @type {function(this: Node, !Node): !Node} */ +const appendChildMethod = method(descriptors.appendChild); +/** @type {function(this: Node): !NodeList} */ +const childNodesGetter = getter(descriptors.childNodes, function() { return this.childNodes; }); +/** @type {function(this: Node, boolean=): !Node} */ +const cloneNodeMethod = method(descriptors.cloneNode); +/** @type {function(this: Node, ?Node): boolean} */ +const containsMethod = method(descriptors.contains); +/** @type {function(this: Node): ?Node} */ +const firstChildGetter = getter(descriptors.firstChild, function() { return this.firstChild; }); +/** @type {function(this: Node, !Node, ?Node): !Node} */ +const insertBeforeMethod = method(descriptors.insertBefore); +/** @type {function(this: Node): boolean} */ +const isConnectedGetter = getter(descriptors.isConnected, function() { return this.isConnected; }); +/** @type {function(this: Node): ?Node} */ +const nextSiblingGetter = getter(descriptors.nextSibling, function() { return this.nextSibling; }); +/** @type {function(this: Node): number} */ +const nodeTypeGetter = getter(descriptors.nodeType, function() { return this.nodeType; }); +/** @type {function(this: Node): ?Document} */ +const ownerDocumentGetter = getter(descriptors.ownerDocument, function() { return this.ownerDocument; }); +/** @type {function(this: Node): ?Node} */ +const parentNodeGetter = getter(descriptors.parentNode, function() { return this.parentNode; }); +/** @type {function(this: Node, !Node): !Node} */ +const removeChildMethod = method(descriptors.removeChild); +/** @type {function(this: Node, !Node, !Node): !Node} */ +const replaceChildMethod = method(descriptors.replaceChild); +/** @type {function(this: Node): ?string} */ +const textContentGetter = getter(descriptors.textContent, function() { return this.textContent; }); + +export const proxy = { + /** @type {function(!Node, !Node): !Node} */ + appendChild: (node, deep) => appendChildMethod.call(node, deep), + /** @type {function(!Node): !NodeList} */ + childNodes: node => childNodesGetter.call(node), + /** @type {function(!Node, boolean=): !Node} */ + cloneNode: (node, deep) => cloneNodeMethod.call(node, deep), + /** @type {function(!Node, ?Node): boolean} */ + contains: (node, other) => containsMethod.call(node, other), + /** @type {function(!Node): ?Node} */ + firstChild: node => firstChildGetter.call(node), + /** @type {function(!Node, !Node, ?Node): !Node} */ + insertBefore: (node, newChild, refChild) => insertBeforeMethod.call(node, newChild, refChild), + /** @type {function(!Node): boolean} */ + isConnected: node => isConnectedGetter.call(node), + /** @type {function(!Node): ?Node} */ + nextSibling: node => nextSiblingGetter.call(node), + /** @type {function(!Node): number} */ + nodeType: node => nodeTypeGetter.call(node), + /** @type {function(!Node): ?Document} */ + ownerDocument: node => ownerDocumentGetter.call(node), + /** @type {function(!Node): ?Node} */ + parentNode: node => parentNodeGetter.call(node), + /** @type {function(!Node, !Node): !Node} */ + removeChild: (node, deep) => removeChildMethod.call(node, deep), + /** @type {function(!Node, !Node, !Node): !Node} */ + replaceChild: (node, newChild, oldChild) => replaceChildMethod.call(node, newChild, oldChild), + /** @type {function(!Node): ?string} */ + textContent: (node) => textContentGetter.call(node), +}; diff --git a/src/Environment/Utilities.js b/src/Environment/Utilities.js new file mode 100644 index 0000000..08152cd --- /dev/null +++ b/src/Environment/Utilities.js @@ -0,0 +1,31 @@ +/** + * @license + * Copyright (c) 2016 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 + */ + +/** + * @template T + * @param {T} o + * @param {string|symbol} p + * @return {!ObjectPropertyDescriptor|undefined} + */ +export const getDescriptor = (o, p) => Object.getOwnPropertyDescriptor(o, p); + +/** + * @template THIS + * @param {!ObjectPropertyDescriptor|undefined} descriptor + * @param {function(this: THIS): ?} fallback + * @returns {function(this: THIS): ?} + */ +export const getter = (descriptor, fallback) => descriptor && descriptor.get ? descriptor.get : fallback; + +/** + * @param {!ObjectPropertyDescriptor|undefined} descriptor + * @returns {?} + */ +export const method = descriptor => descriptor && descriptor.value; diff --git a/src/Patch/Document.js b/src/Patch/Document.js index 582f492..dd6485e 100644 --- a/src/Patch/Document.js +++ b/src/Patch/Document.js @@ -8,17 +8,26 @@ * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ -import Native from './Native.js'; +import { + descriptors as DocumentDesc, + proto as DocumentProto, + proxy as DocumentProxy, +} from '../Environment/Document.js'; + import CustomElementInternals from '../CustomElementInternals.js'; import * as Utilities from '../Utilities.js'; -import PatchParentNode from './Interface/ParentNode.js'; +import { + default as PatchParentNode, + PrependType, + AppendType, +} from './Interface/ParentNode.js'; /** * @param {!CustomElementInternals} internals */ export default function(internals) { - Utilities.setPropertyUnchecked(Document.prototype, 'createElement', + Utilities.setPropertyUnchecked(DocumentProto, 'createElement', /** * @this {Document} * @param {string} localName @@ -34,12 +43,12 @@ export default function(internals) { } const result = /** @type {!Element} */ - (Native.Document_createElement.call(this, localName)); + (DocumentProxy.createElement(this, localName)); internals.patchElement(result); return result; }); - Utilities.setPropertyUnchecked(Document.prototype, 'importNode', + Utilities.setPropertyUnchecked(DocumentProto, 'importNode', /** * @this {Document} * @param {!Node} node @@ -47,7 +56,7 @@ export default function(internals) { * @return {!Node} */ function(node, deep) { - const clone = /** @type {!Node} */ (Native.Document_importNode.call(this, node, !!deep)); + const clone = /** @type {!Node} */ (DocumentProxy.importNode(this, node, !!deep)); // Only create custom elements if this document is associated with the registry. if (!this.__CE_hasRegistry) { internals.patchTree(clone); @@ -59,7 +68,7 @@ export default function(internals) { const NS_HTML = "http://www.w3.org/1999/xhtml"; - Utilities.setPropertyUnchecked(Document.prototype, 'createElementNS', + Utilities.setPropertyUnchecked(DocumentProto, 'createElementNS', /** * @this {Document} * @param {?string} namespace @@ -76,13 +85,13 @@ export default function(internals) { } const result = /** @type {!Element} */ - (Native.Document_createElementNS.call(this, namespace, localName)); + (DocumentProxy.createElementNS(this, namespace, localName)); internals.patchElement(result); return result; }); - PatchParentNode(internals, Document.prototype, { - prepend: Native.Document_prepend, - append: Native.Document_append, + PatchParentNode(internals, DocumentProto, { + prepend: /** @type {PrependType|undefined} */ ((DocumentDesc.prepend || {}).value), + append: /** @type {AppendType|undefined} */ ((DocumentDesc.append || {}).value), }); }; diff --git a/src/Patch/DocumentFragment.js b/src/Patch/DocumentFragment.js index da1e893..8bd87ac 100644 --- a/src/Patch/DocumentFragment.js +++ b/src/Patch/DocumentFragment.js @@ -9,15 +9,22 @@ */ import CustomElementInternals from '../CustomElementInternals.js'; -import Native from './Native.js'; -import PatchParentNode from './Interface/ParentNode.js'; +import { + descriptors as DocumentFragmentDesc, + proto as DocumentFragmentProto, +} from '../Environment/DocumentFragment.js'; +import { + default as PatchParentNode, + PrependType, + AppendType, +} from './Interface/ParentNode.js'; /** * @param {!CustomElementInternals} internals */ export default function(internals) { - PatchParentNode(internals, DocumentFragment.prototype, { - prepend: Native.DocumentFragment_prepend, - append: Native.DocumentFragment_append, + PatchParentNode(internals, DocumentFragmentProto, { + prepend: /** @type {PrependType|undefined} */ ((DocumentFragmentDesc.prepend || {}).value), + append: /** @type {AppendType|undefined} */ ((DocumentFragmentDesc.append || {}).value), }); }; diff --git a/src/Patch/Element.js b/src/Patch/Element.js index e06e374..aafa446 100644 --- a/src/Patch/Element.js +++ b/src/Patch/Element.js @@ -8,27 +8,50 @@ * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ -import Native from './Native.js'; +import {proxy as DocumentProxy} from '../Environment/Document.js'; +import { + descriptors as ElementDesc, + proto as ElementProto, + proxy as ElementProxy, +} from '../Environment/Element.js'; +import { + descriptors as HTMLElementDesc, + proto as HTMLElementProto, +} from '../Environment/HTMLElement.js'; +import {proxy as HTMLTemplateElementProxy} from '../Environment/HTMLTemplateElement.js'; +import {proxy as NodeProxy} from '../Environment/Node.js'; + import CustomElementInternals from '../CustomElementInternals.js'; import CEState from '../CustomElementState.js'; import * as Utilities from '../Utilities.js'; -import PatchParentNode from './Interface/ParentNode.js'; -import PatchChildNode from './Interface/ChildNode.js'; +import { + default as PatchParentNode, + PrependType, + AppendType, +} from './Interface/ParentNode.js'; +import { + default as PatchChildNode, + BeforeType, + AfterType, + ReplaceWithType, + RemoveType, +}from './Interface/ChildNode.js'; /** * @param {!CustomElementInternals} internals */ export default function(internals) { - if (Native.Element_attachShadow) { - Utilities.setPropertyUnchecked(Element.prototype, 'attachShadow', + if (ElementDesc.attachShadow) { + Utilities.setPropertyUnchecked(ElementProto, 'attachShadow', /** * @this {Element} * @param {!{mode: string}} init - * @return {ShadowRoot} + * @return {!ShadowRoot} */ function(init) { - const shadowRoot = Native.Element_attachShadow.call(this, init); + const shadowRoot = /** @type {!ShadowRoot} */ ( + ElementProxy.attachShadow(this, init)); internals.patchNode(shadowRoot); this.__CE_shadowRoot = shadowRoot; return shadowRoot; @@ -73,7 +96,7 @@ export default function(internals) { // Only create custom elements if this element's owner document is // associated with the registry. - if (!this.ownerDocument.__CE_hasRegistry) { + if (!NodeProxy.ownerDocument(this).__CE_hasRegistry) { internals.patchTree(this); } else { internals.patchAndUpgradeTree(this); @@ -83,11 +106,14 @@ export default function(internals) { }); } - if (Native.Element_innerHTML && Native.Element_innerHTML.get) { - patch_innerHTML(Element.prototype, Native.Element_innerHTML); - } else if (Native.HTMLElement_innerHTML && Native.HTMLElement_innerHTML.get) { - patch_innerHTML(HTMLElement.prototype, Native.HTMLElement_innerHTML); + if (ElementDesc.innerHTML && ElementDesc.innerHTML.get) { + patch_innerHTML(ElementProto, ElementDesc.innerHTML); + } else if (HTMLElementDesc.innerHTML && HTMLElementDesc.innerHTML.get) { + patch_innerHTML(HTMLElementProto, HTMLElementDesc.innerHTML); } else { + // In this case, `innerHTML` has no exposed getter but still exists. Rather + // than using the environment proxy, we have to get and set it directly. + internals.addElementPatch(function(element) { patch_innerHTML(element, { enumerable: true, @@ -96,9 +122,8 @@ export default function(internals) { // of the element and returning the resulting element's `innerHTML`. // TODO: Is this too expensive? get: /** @this {Element} */ function() { - return /** @type {!Element} */ ( - Native.Node_cloneNode.call(this, true)) - .innerHTML; + return /** @type {Element} */ ( + NodeProxy.cloneNode(this, true)).innerHTML; }, // Implements setting `innerHTML` by creating an unpatched element, // setting `innerHTML` of that element and replacing the target @@ -107,23 +132,27 @@ export default function(internals) { // NOTE: re-route to `content` for `template` elements. // We need to do this because `template.appendChild` does not // route into `template.content`. - const isTemplate = (this.localName === 'template'); + const localName = ElementProxy.localName(this); + const namespaceURI = ElementProxy.namespaceURI(this); + const isTemplate = (localName === 'template'); /** @type {!Node} */ - const content = isTemplate ? (/** @type {!HTMLTemplateElement} */ - (this)).content : this; + const content = + isTemplate + ? HTMLTemplateElementProxy.content(/** @type {!HTMLTemplateElement} */ (this)) + : this; /** @type {!Element} */ - const rawElement = Native.Document_createElementNS.call(document, - this.namespaceURI, this.localName); + const rawElement = DocumentProxy.createElementNS(document, namespaceURI, localName); rawElement.innerHTML = assignedValue; - while (content.childNodes.length > 0) { - Native.Node_removeChild.call(content, content.childNodes[0]); + while (NodeProxy.childNodes(content).length > 0) { + NodeProxy.removeChild(content, content.childNodes[0]); } const container = isTemplate ? - /** @type {!HTMLTemplateElement} */ (rawElement).content : + HTMLTemplateElementProxy.content( + /** @type {!HTMLTemplateElement} */ (rawElement)) : rawElement; - while (container.childNodes.length > 0) { - Native.Node_appendChild.call(content, container.childNodes[0]); + while (NodeProxy.childNodes(container).length > 0) { + NodeProxy.appendChild(content, container.childNodes[0]); } }, }); @@ -131,7 +160,7 @@ export default function(internals) { } - Utilities.setPropertyUnchecked(Element.prototype, 'setAttribute', + Utilities.setPropertyUnchecked(ElementProto, 'setAttribute', /** * @this {Element} * @param {string} name @@ -140,16 +169,16 @@ export default function(internals) { function(name, newValue) { // Fast path for non-custom elements. if (this.__CE_state !== CEState.custom) { - return Native.Element_setAttribute.call(this, name, newValue); + return ElementProxy.setAttribute(this, name, newValue); } - const oldValue = Native.Element_getAttribute.call(this, name); - Native.Element_setAttribute.call(this, name, newValue); - newValue = Native.Element_getAttribute.call(this, name); - internals.attributeChangedCallback(this, name, oldValue, newValue, null); + const before = ElementProxy.getAttribute(this, name); + ElementProxy.setAttribute(this, name, newValue); + const after = ElementProxy.getAttribute(this, name); + internals.attributeChangedCallback(this, name, before, after, null); }); - Utilities.setPropertyUnchecked(Element.prototype, 'setAttributeNS', + Utilities.setPropertyUnchecked(ElementProto, 'setAttributeNS', /** * @this {Element} * @param {?string} namespace @@ -159,16 +188,16 @@ export default function(internals) { function(namespace, name, newValue) { // Fast path for non-custom elements. if (this.__CE_state !== CEState.custom) { - return Native.Element_setAttributeNS.call(this, namespace, name, newValue); + return ElementProxy.setAttributeNS(this, namespace, name, newValue); } - const oldValue = Native.Element_getAttributeNS.call(this, namespace, name); - Native.Element_setAttributeNS.call(this, namespace, name, newValue); - newValue = Native.Element_getAttributeNS.call(this, namespace, name); - internals.attributeChangedCallback(this, name, oldValue, newValue, namespace); + const before = ElementProxy.getAttributeNS(this, namespace, name); + ElementProxy.setAttributeNS(this, namespace, name, newValue); + const after = ElementProxy.getAttributeNS(this, namespace, name); + internals.attributeChangedCallback(this, name, before, after, namespace); }); - Utilities.setPropertyUnchecked(Element.prototype, 'removeAttribute', + Utilities.setPropertyUnchecked(ElementProto, 'removeAttribute', /** * @this {Element} * @param {string} name @@ -176,17 +205,17 @@ export default function(internals) { function(name) { // Fast path for non-custom elements. if (this.__CE_state !== CEState.custom) { - return Native.Element_removeAttribute.call(this, name); + return ElementProxy.removeAttribute(this, name); } - const oldValue = Native.Element_getAttribute.call(this, name); - Native.Element_removeAttribute.call(this, name); + const oldValue = ElementProxy.getAttribute(this, name); + ElementProxy.removeAttribute(this, name); if (oldValue !== null) { internals.attributeChangedCallback(this, name, oldValue, null, null); } }); - Utilities.setPropertyUnchecked(Element.prototype, 'removeAttributeNS', + Utilities.setPropertyUnchecked(ElementProto, 'removeAttributeNS', /** * @this {Element} * @param {?string} namespace @@ -195,15 +224,15 @@ export default function(internals) { function(namespace, name) { // Fast path for non-custom elements. if (this.__CE_state !== CEState.custom) { - return Native.Element_removeAttributeNS.call(this, namespace, name); + return ElementProxy.removeAttributeNS(this, namespace, name); } - const oldValue = Native.Element_getAttributeNS.call(this, namespace, name); - Native.Element_removeAttributeNS.call(this, namespace, name); + const oldValue = ElementProxy.getAttributeNS(this, namespace, name); + ElementProxy.removeAttributeNS(this, namespace, name); // In older browsers, `Element#getAttributeNS` may return the empty string // instead of null if the attribute does not exist. For details, see; // https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttributeNS#Notes - const newValue = Native.Element_getAttributeNS.call(this, namespace, name); + const newValue = ElementProxy.getAttributeNS(this, namespace, name); if (oldValue !== newValue) { internals.attributeChangedCallback(this, name, oldValue, newValue, namespace); } @@ -234,10 +263,10 @@ export default function(internals) { }); } - if (Native.HTMLElement_insertAdjacentElement) { - patch_insertAdjacentElement(HTMLElement.prototype, Native.HTMLElement_insertAdjacentElement); - } else if (Native.Element_insertAdjacentElement) { - patch_insertAdjacentElement(Element.prototype, Native.Element_insertAdjacentElement); + if (HTMLElementDesc.insertAdjacentElement && HTMLElementDesc.insertAdjacentElement.value) { + patch_insertAdjacentElement(HTMLElementProto, HTMLElementDesc.insertAdjacentElement.value); + } else if (ElementDesc.insertAdjacentElement && ElementDesc.insertAdjacentElement.value) { + patch_insertAdjacentElement(ElementProto, ElementDesc.insertAdjacentElement.value); } else { console.warn('Custom Elements: `Element#insertAdjacentElement` was not patched.'); } @@ -293,24 +322,24 @@ export default function(internals) { }); } - if (Native.HTMLElement_insertAdjacentHTML) { - patch_insertAdjacentHTML(HTMLElement.prototype, Native.HTMLElement_insertAdjacentHTML); - } else if (Native.Element_insertAdjacentHTML) { - patch_insertAdjacentHTML(Element.prototype, Native.Element_insertAdjacentHTML); + if (HTMLElementDesc.insertAdjacentHTML) { + patch_insertAdjacentHTML(HTMLElementProto, HTMLElementDesc.insertAdjacentHTML.value); + } else if (ElementDesc.insertAdjacentHTML) { + patch_insertAdjacentHTML(ElementProto, ElementDesc.insertAdjacentHTML.value); } else { console.warn('Custom Elements: `Element#insertAdjacentHTML` was not patched.'); } - PatchParentNode(internals, Element.prototype, { - prepend: Native.Element_prepend, - append: Native.Element_append, + PatchParentNode(internals, ElementProto, { + prepend: /** @type {PrependType|undefined} */ ((ElementDesc.prepend || {}).value), + append: /** @type {AppendType|undefined} */ ((ElementDesc.append || {}).value), }); - PatchChildNode(internals, Element.prototype, { - before: Native.Element_before, - after: Native.Element_after, - replaceWith: Native.Element_replaceWith, - remove: Native.Element_remove, + PatchChildNode(internals, ElementProto, { + before: /** @type {BeforeType|undefined} */ ((ElementDesc.before || {}).value), + after: /** @type {AfterType|undefined} */ ((ElementDesc.after || {}).value), + replaceWith: /** @type {ReplaceWithType|undefined} */ ((ElementDesc.replaceWith || {}).value), + remove: /** @type {RemoveType|undefined} */ ((ElementDesc.remove || {}).value), }); }; diff --git a/src/Patch/HTMLElement.js b/src/Patch/HTMLElement.js index df00ab5..ec4c8cc 100644 --- a/src/Patch/HTMLElement.js +++ b/src/Patch/HTMLElement.js @@ -8,7 +8,9 @@ * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ -import Native from './Native.js'; +import {proxy as DocumentProxy} from '../Environment/Document.js'; +import {proto as HTMLElementProto} from '../Environment/HTMLElement.js'; + import CustomElementInternals from '../CustomElementInternals.js'; import CEState from '../CustomElementState.js'; import AlreadyConstructedMarker from '../AlreadyConstructedMarker.js'; @@ -35,7 +37,7 @@ export default function(internals) { const constructionStack = definition.constructionStack; if (constructionStack.length === 0) { - const element = /** @type {!HTMLElement} */ (Native.Document_createElement.call(document, definition.localName)); + const element = /** @type {!HTMLElement} */ (DocumentProxy.createElement(document, definition.localName)); Object.setPrototypeOf(element, constructor.prototype); element.__CE_state = CEState.custom; element.__CE_definition = definition; @@ -57,7 +59,7 @@ export default function(internals) { return toConstructElement; } - HTMLElement.prototype = Native.HTMLElement.prototype; + HTMLElement.prototype = HTMLElementProto; // Safari 9 has `writable: false` on the propertyDescriptor // Make it writable so that TypeScript can patch up the // constructor in the ES5 compiled code. diff --git a/src/Patch/Interface/ChildNode.js b/src/Patch/Interface/ChildNode.js index 3945674..cc676b0 100644 --- a/src/Patch/Interface/ChildNode.js +++ b/src/Patch/Interface/ChildNode.js @@ -9,22 +9,27 @@ */ import CustomElementInternals from '../../CustomElementInternals.js'; +import {proxy as NodeProxy} from '../../Environment/Node.js'; import * as Utilities from '../../Utilities.js'; -/** - * @typedef {{ - * before: !function(...(!Node|string)), - * after: !function(...(!Node|string)), - * replaceWith: !function(...(!Node|string)), - * remove: !function(), - * }} - */ -let ChildNodeNativeMethods; +/** @typedef {!function(...(!Node|string))} */ +export let BeforeType; +/** @typedef {!function(...(!Node|string))} */ +export let AfterType; +/** @typedef {!function(...(!Node|string))} */ +export let ReplaceWithType; +/** @typedef {!function()} */ +export let RemoveType; /** * @param {!CustomElementInternals} internals * @param {!Object} destination - * @param {!ChildNodeNativeMethods} builtIn + * @param {!{ + * before: (BeforeType|undefined), + * after: (AfterType|undefined), + * replaceWith: (ReplaceWithType|undefined), + * remove: (RemoveType|undefined), + * }} builtIn */ export default function(internals, destination, builtIn) { /** @@ -53,7 +58,7 @@ export default function(internals, destination, builtIn) { } if (node instanceof DocumentFragment) { - for (let child = node.firstChild; child; child = child.nextSibling) { + for (let child = NodeProxy.firstChild(node); child; child = NodeProxy.nextSibling(child)) { flattenedNodes.push(child); } } else { @@ -82,7 +87,7 @@ export default function(internals, destination, builtIn) { Utilities.setPropertyUnchecked(destination, 'before', beforeAfterPatch(builtIn.before)); } - if (builtIn.before !== undefined) { + if (builtIn.after !== undefined) { Utilities.setPropertyUnchecked(destination, 'after', beforeAfterPatch(builtIn.after)); } @@ -113,7 +118,7 @@ export default function(internals, destination, builtIn) { } if (node instanceof DocumentFragment) { - for (let child = node.firstChild; child; child = child.nextSibling) { + for (let child = NodeProxy.firstChild(node); child; child = NodeProxy.nextSibling(child)) { flattenedNodes.push(child); } } else { diff --git a/src/Patch/Interface/ParentNode.js b/src/Patch/Interface/ParentNode.js index 19ec72b..ace2928 100644 --- a/src/Patch/Interface/ParentNode.js +++ b/src/Patch/Interface/ParentNode.js @@ -9,25 +9,26 @@ */ import CustomElementInternals from '../../CustomElementInternals.js'; +import {proxy as NodeProxy} from '../../Environment/Node.js'; import * as Utilities from '../../Utilities.js'; -/** - * @typedef {{ - * prepend: !function(...(!Node|string)), - * append: !function(...(!Node|string)), - * }} - */ -let ParentNodeNativeMethods; +/** @typedef {!function(...(!Node|string))} */ +export let PrependType; +/** @typedef {!function(...(!Node|string))} */ +export let AppendType; /** * @param {!CustomElementInternals} internals * @param {!Object} destination - * @param {!ParentNodeNativeMethods} builtIn + * @param {!{ + * prepend: (PrependType|undefined), + * append: (AppendType|undefined), + * }} builtIn */ export default function(internals, destination, builtIn) { /** - * @param {!function(...(!Node|string))} builtInMethod - * @return {!function(...(!Node|string))} + * @param {PrependType|AppendType} builtInMethod + * @return {PrependType|AppendType} */ function appendPrependPatch(builtInMethod) { return /** @this {!Node} */ function(...nodes) { @@ -51,7 +52,7 @@ export default function(internals, destination, builtIn) { } if (node instanceof DocumentFragment) { - for (let child = node.firstChild; child; child = child.nextSibling) { + for (let child = NodeProxy.firstChild(node); child; child = NodeProxy.nextSibling(child)) { flattenedNodes.push(child); } } else { diff --git a/src/Patch/Native.js b/src/Patch/Native.js deleted file mode 100644 index 5318452..0000000 --- a/src/Patch/Native.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @license - * Copyright (c) 2016 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 - */ - -export default { - Document_createElement: window.Document.prototype.createElement, - Document_createElementNS: window.Document.prototype.createElementNS, - Document_importNode: window.Document.prototype.importNode, - Document_prepend: window.Document.prototype['prepend'], - Document_append: window.Document.prototype['append'], - DocumentFragment_prepend: window.DocumentFragment.prototype['prepend'], - DocumentFragment_append: window.DocumentFragment.prototype['append'], - Node_cloneNode: window.Node.prototype.cloneNode, - Node_appendChild: window.Node.prototype.appendChild, - Node_insertBefore: window.Node.prototype.insertBefore, - Node_removeChild: window.Node.prototype.removeChild, - Node_replaceChild: window.Node.prototype.replaceChild, - Node_textContent: Object.getOwnPropertyDescriptor(window.Node.prototype, 'textContent'), - Element_attachShadow: window.Element.prototype['attachShadow'], - Element_innerHTML: Object.getOwnPropertyDescriptor(window.Element.prototype, 'innerHTML'), - Element_getAttribute: window.Element.prototype.getAttribute, - Element_setAttribute: window.Element.prototype.setAttribute, - Element_removeAttribute: window.Element.prototype.removeAttribute, - Element_getAttributeNS: window.Element.prototype.getAttributeNS, - Element_setAttributeNS: window.Element.prototype.setAttributeNS, - Element_removeAttributeNS: window.Element.prototype.removeAttributeNS, - Element_insertAdjacentElement: window.Element.prototype['insertAdjacentElement'], - Element_insertAdjacentHTML: window.Element.prototype['insertAdjacentHTML'], - Element_prepend: window.Element.prototype['prepend'], - Element_append: window.Element.prototype['append'], - Element_before: window.Element.prototype['before'], - Element_after: window.Element.prototype['after'], - Element_replaceWith: window.Element.prototype['replaceWith'], - Element_remove: window.Element.prototype['remove'], - HTMLElement: window.HTMLElement, - HTMLElement_innerHTML: Object.getOwnPropertyDescriptor(window.HTMLElement.prototype, 'innerHTML'), - HTMLElement_insertAdjacentElement: window.HTMLElement.prototype['insertAdjacentElement'], - HTMLElement_insertAdjacentHTML: window.HTMLElement.prototype['insertAdjacentHTML'], -}; diff --git a/src/Patch/Node.js b/src/Patch/Node.js index 8617ac0..ed456e5 100644 --- a/src/Patch/Node.js +++ b/src/Patch/Node.js @@ -8,7 +8,13 @@ * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ -import Native from './Native.js'; +import {proxy as DocumentProxy} from '../Environment/Document.js'; +import { + descriptors as NodeDesc, + proto as NodeProto, + proxy as NodeProxy, +} from '../Environment/Node.js'; + import CustomElementInternals from '../CustomElementInternals.js'; import * as Utilities from '../Utilities.js'; @@ -19,7 +25,7 @@ export default function(internals) { // `Node#nodeValue` is implemented on `Attr`. // `Node#textContent` is implemented on `Attr`, `Element`. - Utilities.setPropertyUnchecked(Node.prototype, 'insertBefore', + Utilities.setPropertyUnchecked(NodeProto, 'insertBefore', /** * @this {Node} * @param {!Node} node @@ -28,8 +34,8 @@ export default function(internals) { */ function(node, refNode) { if (node instanceof DocumentFragment) { - const insertedNodes = Array.prototype.slice.apply(node.childNodes); - const nativeResult = Native.Node_insertBefore.call(this, node, refNode); + const insertedNodes = Array.prototype.slice.apply(NodeProxy.childNodes(node)); + const nativeResult = NodeProxy.insertBefore(this, node, refNode); // DocumentFragments can't be connected, so `disconnectTree` will never // need to be called on a DocumentFragment's children after inserting it. @@ -44,7 +50,7 @@ export default function(internals) { } const nodeWasConnected = Utilities.isConnected(node); - const nativeResult = Native.Node_insertBefore.call(this, node, refNode); + const nativeResult = NodeProxy.insertBefore(this, node, refNode); if (nodeWasConnected) { internals.disconnectTree(node); @@ -57,7 +63,7 @@ export default function(internals) { return nativeResult; }); - Utilities.setPropertyUnchecked(Node.prototype, 'appendChild', + Utilities.setPropertyUnchecked(NodeProto, 'appendChild', /** * @this {Node} * @param {!Node} node @@ -65,8 +71,8 @@ export default function(internals) { */ function(node) { if (node instanceof DocumentFragment) { - const insertedNodes = Array.prototype.slice.apply(node.childNodes); - const nativeResult = Native.Node_appendChild.call(this, node); + const insertedNodes = Array.prototype.slice.apply(NodeProxy.childNodes(node)); + const nativeResult = NodeProxy.appendChild(this, node); // DocumentFragments can't be connected, so `disconnectTree` will never // need to be called on a DocumentFragment's children after inserting it. @@ -81,7 +87,7 @@ export default function(internals) { } const nodeWasConnected = Utilities.isConnected(node); - const nativeResult = Native.Node_appendChild.call(this, node); + const nativeResult = NodeProxy.appendChild(this, node); if (nodeWasConnected) { internals.disconnectTree(node); @@ -94,17 +100,17 @@ export default function(internals) { return nativeResult; }); - Utilities.setPropertyUnchecked(Node.prototype, 'cloneNode', + Utilities.setPropertyUnchecked(NodeProto, 'cloneNode', /** * @this {Node} * @param {boolean=} deep * @return {!Node} */ function(deep) { - const clone = Native.Node_cloneNode.call(this, !!deep); + const clone = NodeProxy.cloneNode(this, !!deep); // Only create custom elements if this element's owner document is // associated with the registry. - if (!this.ownerDocument.__CE_hasRegistry) { + if (!NodeProxy.ownerDocument(this).__CE_hasRegistry) { internals.patchTree(clone); } else { internals.patchAndUpgradeTree(clone); @@ -112,7 +118,7 @@ export default function(internals) { return clone; }); - Utilities.setPropertyUnchecked(Node.prototype, 'removeChild', + Utilities.setPropertyUnchecked(NodeProto, 'removeChild', /** * @this {Node} * @param {!Node} node @@ -120,7 +126,7 @@ export default function(internals) { */ function(node) { const nodeWasConnected = Utilities.isConnected(node); - const nativeResult = Native.Node_removeChild.call(this, node); + const nativeResult = NodeProxy.removeChild(this, node); if (nodeWasConnected) { internals.disconnectTree(node); @@ -129,7 +135,7 @@ export default function(internals) { return nativeResult; }); - Utilities.setPropertyUnchecked(Node.prototype, 'replaceChild', + Utilities.setPropertyUnchecked(NodeProto, 'replaceChild', /** * @this {Node} * @param {!Node} nodeToInsert @@ -138,8 +144,8 @@ export default function(internals) { */ function(nodeToInsert, nodeToRemove) { if (nodeToInsert instanceof DocumentFragment) { - const insertedNodes = Array.prototype.slice.apply(nodeToInsert.childNodes); - const nativeResult = Native.Node_replaceChild.call(this, nodeToInsert, nodeToRemove); + const insertedNodes = Array.prototype.slice.apply(NodeProxy.childNodes(nodeToInsert)); + const nativeResult = NodeProxy.replaceChild(this, nodeToInsert, nodeToRemove); // DocumentFragments can't be connected, so `disconnectTree` will never // need to be called on a DocumentFragment's children after inserting it. @@ -155,7 +161,7 @@ export default function(internals) { } const nodeToInsertWasConnected = Utilities.isConnected(nodeToInsert); - const nativeResult = Native.Node_replaceChild.call(this, nodeToInsert, nodeToRemove); + const nativeResult = NodeProxy.replaceChild(this, nodeToInsert, nodeToRemove); const thisIsConnected = Utilities.isConnected(this); if (thisIsConnected) { @@ -181,7 +187,7 @@ export default function(internals) { 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) { + if (NodeProxy.nodeType(this) === Node.TEXT_NODE) { baseDescriptor.set.call(this, assignedValue); return; } @@ -189,10 +195,10 @@ export default function(internals) { let removedNodes = undefined; // Checking for `firstChild` is faster than reading `childNodes.length` // to compare with 0. - if (this.firstChild) { + if (NodeProxy.firstChild(this)) { // Using `childNodes` is faster than `children`, even though we only // care about elements. - const childNodes = this.childNodes; + const childNodes = NodeProxy.childNodes(this); const childNodesLength = childNodes.length; if (childNodesLength > 0 && Utilities.isConnected(this)) { // Copying an array by iterating is faster than using slice. @@ -214,8 +220,8 @@ export default function(internals) { }); } - if (Native.Node_textContent && Native.Node_textContent.get) { - patch_textContent(Node.prototype, Native.Node_textContent); + if (NodeDesc.textContent && NodeDesc.textContent.get) { + patch_textContent(NodeProto, NodeDesc.textContent); } else { internals.addNodePatch(function(element) { patch_textContent(element, { @@ -227,24 +233,29 @@ export default function(internals) { /** @type {!Array} */ const parts = []; - for (let i = 0; i < this.childNodes.length; i++) { - const childNode = this.childNodes[i]; - if (childNode.nodeType === Node.COMMENT_NODE) { + const childNodes = NodeProxy.childNodes(this); + for (let i = 0; i < childNodes.length; i++) { + const childNode = childNodes[i]; + if (NodeProxy.nodeType(childNode) === Node.COMMENT_NODE) { continue; } - parts.push(childNode.textContent); + const textContent = NodeProxy.textContent(childNode); + if (textContent) { + parts.push(textContent); + } } return parts.join(''); }, set: /** @this {Node} */ function(assignedValue) { - while (this.firstChild) { - Native.Node_removeChild.call(this, this.firstChild); + let child; + while (child = NodeProxy.firstChild(this)) { + NodeProxy.removeChild(this, child); } // `textContent = null | undefined | ''` does not result in // a TextNode childNode if (assignedValue != null && assignedValue !== '') { - Native.Node_appendChild.call(this, document.createTextNode(assignedValue)); + NodeProxy.appendChild(this, DocumentProxy.createTextNode(document, assignedValue)); } }, }); diff --git a/src/Utilities.js b/src/Utilities.js index 27f3be3..4a4a230 100644 --- a/src/Utilities.js +++ b/src/Utilities.js @@ -8,6 +8,14 @@ * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt */ +import {proxy as DocumentProxy} from './Environment/Document.js'; +import {proxy as ElementProxy} from './Environment/Element.js'; +import {proxy as HTMLElementProxy} from './Environment/HTMLElement.js'; +import { + descriptors as NodeDesc, + proxy as NodeProxy, +} from './Environment/Node.js'; + const reservedTagList = new Set([ 'annotation-xml', 'color-profile', @@ -29,9 +37,18 @@ export function isValidCustomElementName(localName) { return !reserved && validForm; } -// Note, IE11 doesn't have `document.contains`. -const nativeContains = document.contains ? document.contains.bind(document) : - document.documentElement.contains.bind(document.documentElement); +// Note, in IE11 `#contains` is on HTMLElement instead of Node. +/** @type {function(?Node): boolean} */ +const nativeDocumentContains = NodeDesc.contains ? + (node) => NodeProxy.contains(document, node) : + (node) => { + const documentElement = DocumentProxy.documentElement(document); + if (documentElement === null) { + return false; + } + + return HTMLElementProxy.contains(documentElement, node); + }; /** * @param {!Node} node @@ -39,19 +56,19 @@ const nativeContains = document.contains ? document.contains.bind(document) : */ export function isConnected(node) { // Use `Node#isConnected`, if defined. - const nativeValue = node.isConnected; + const nativeValue = NodeProxy.isConnected(node); if (nativeValue !== undefined) { return nativeValue; } // Optimization: It's significantly faster here to try to use `contains`, - // especially on Edge/IE/ - if (nativeContains(node)) { + // especially on Edge/IE. + if (nativeDocumentContains(node)) { return true; } /** @type {?Node|undefined} */ let current = node; while (current && !(current.__CE_isImportDocument || current instanceof Document)) { - current = current.parentNode || (window.ShadowRoot && current instanceof ShadowRoot ? current.host : undefined); + current = NodeProxy.parentNode(current) || (window.ShadowRoot && current instanceof ShadowRoot ? current.host : undefined); } return !!(current && (current.__CE_isImportDocument || current instanceof Document)); } @@ -63,10 +80,10 @@ export function isConnected(node) { */ function nextSiblingOrAncestorSibling(root, start) { let node = start; - while (node && node !== root && !node.nextSibling) { - node = node.parentNode; + while (node && node !== root && !NodeProxy.nextSibling(node)) { + node = NodeProxy.parentNode(node); } - return (!node || node === root) ? null : node.nextSibling; + return (!node || node === root) ? null : NodeProxy.nextSibling(node); } /** @@ -75,7 +92,7 @@ function nextSiblingOrAncestorSibling(root, start) { * @return {?Node} */ function nextNode(root, start) { - return start.firstChild ? start.firstChild : nextSiblingOrAncestorSibling(root, start); + return NodeProxy.firstChild(start) || nextSiblingOrAncestorSibling(root, start); } /** @@ -86,13 +103,13 @@ function nextNode(root, start) { export function walkDeepDescendantElements(root, callback, visitedImports) { let node = root; while (node) { - if (node.nodeType === Node.ELEMENT_NODE) { + if (NodeProxy.nodeType(node) === Node.ELEMENT_NODE) { const element = /** @type {!Element} */(node); callback(element); - const localName = element.localName; - if (localName === 'link' && element.getAttribute('rel') === 'import') { + const localName = ElementProxy.localName(element); + if (localName === 'link' && ElementProxy.getAttribute(element, 'rel') === 'import') { // If this import (polyfilled or not) has it's root node available, // walk it. const importNode = /** @type {!Node} */ (element.import); @@ -103,7 +120,7 @@ export function walkDeepDescendantElements(root, callback, visitedImports) { // Prevent multiple walks of the same import root. visitedImports.add(importNode); - for (let child = importNode.firstChild; child; child = child.nextSibling) { + for (let child = NodeProxy.firstChild(importNode); child; child = NodeProxy.nextSibling(child)) { walkDeepDescendantElements(child, callback, visitedImports); } } @@ -125,7 +142,7 @@ export function walkDeepDescendantElements(root, callback, visitedImports) { // Walk shadow roots. const shadowRoot = element.__CE_shadowRoot; if (shadowRoot) { - for (let child = shadowRoot.firstChild; child; child = child.nextSibling) { + for (let child = NodeProxy.firstChild(shadowRoot); child; child = NodeProxy.nextSibling(child)) { walkDeepDescendantElements(child, callback, visitedImports); } } diff --git a/tests/PatchMonitor.js b/tests/PatchMonitor.js new file mode 100644 index 0000000..d67ad49 --- /dev/null +++ b/tests/PatchMonitor.js @@ -0,0 +1,89 @@ +/** + * @license + * Copyright (c) 2016 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 + */ + +window.PatchMonitor = (function() { + let enableGuard = false; + let insideGuard = false; + + function createGuard({original, target, propertyName}) { + return function() { + if (!enableGuard) { + return original.apply(this, arguments); + } + + try { + if (insideGuard) { + throw new Error(`Unexpected access of '${propertyName}'.`); + } + insideGuard = true; + return original.apply(this, arguments); + } finally { + insideGuard = false; + } + }; + } + + const PatchMonitor = { + runGuarded(fn) { + let oldState = enableGuard; + enableGuard = true; + try { + fn(); + } finally { + enableGuard = oldState; + } + }, + + wrap(targets) { + for (let targetIndex = 0; targetIndex < targets.length; targetIndex++) { + const target = targets[targetIndex]; + if (target === undefined) continue; + + const propertyNames = Object.getOwnPropertyNames(target); + for (let propertyNameIndex = 0; propertyNameIndex < propertyNames.length; propertyNameIndex++) { + const propertyName = propertyNames[propertyNameIndex]; + + const descriptor = Object.getOwnPropertyDescriptor(target, propertyName); + const newDescriptor = { + configurable: descriptor.configurable, + enumerable: descriptor.enumerable, + }; + + if (descriptor.value && descriptor.value instanceof Function) { + const original = descriptor.value; + newDescriptor.value = createGuard({ + original, target, propertyName + }); + } + + if (descriptor.get) { + const original = descriptor.get; + newDescriptor.get = createGuard({ + original, target, propertyName + }); + } + + if (descriptor.set) { + const original = descriptor.set; + newDescriptor.set = createGuard({ + original, target, propertyName + }); + } + + if (descriptor.configurable) { + Object.defineProperty(target, propertyName, newDescriptor); + } + } + } + }, + }; + + return PatchMonitor; +})(); diff --git a/tests/html/Document/createElement.html b/tests/html/Document/createElement.html index 4258f3c..262074a 100644 --- a/tests/html/Document/createElement.html +++ b/tests/html/Document/createElement.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/Document/createElementNS.html b/tests/html/Document/createElementNS.html index 8d88732..1f64fc1 100644 --- a/tests/html/Document/createElementNS.html +++ b/tests/html/Document/createElementNS.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/Document/importNode.html b/tests/html/Document/importNode.html index 1b6763d..e574bac 100644 --- a/tests/html/Document/importNode.html +++ b/tests/html/Document/importNode.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/Element/attachShadow.html b/tests/html/Element/attachShadow.html index 45a1712..8f6e104 100644 --- a/tests/html/Element/attachShadow.html +++ b/tests/html/Element/attachShadow.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/Element/innerHTML.html b/tests/html/Element/innerHTML.html index 6b82df7..46d0012 100644 --- a/tests/html/Element/innerHTML.html +++ b/tests/html/Element/innerHTML.html @@ -20,6 +20,20 @@ + + + + diff --git a/tests/html/Element/removeAttribute.html b/tests/html/Element/removeAttribute.html index 884c9ac..ecd0a85 100644 --- a/tests/html/Element/removeAttribute.html +++ b/tests/html/Element/removeAttribute.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/Element/removeAttributeNS.html b/tests/html/Element/removeAttributeNS.html index 441e79e..5e96bcf 100644 --- a/tests/html/Element/removeAttributeNS.html +++ b/tests/html/Element/removeAttributeNS.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/Element/setAttribute.html b/tests/html/Element/setAttribute.html index b2ac6d7..d4ceae6 100644 --- a/tests/html/Element/setAttribute.html +++ b/tests/html/Element/setAttribute.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/Element/setAttributeNS.html b/tests/html/Element/setAttributeNS.html index 36d2d70..490eb97 100644 --- a/tests/html/Element/setAttributeNS.html +++ b/tests/html/Element/setAttributeNS.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/HTMLElement/constructor.html b/tests/html/HTMLElement/constructor.html index dfbaf5e..df3592c 100644 --- a/tests/html/HTMLElement/constructor.html +++ b/tests/html/HTMLElement/constructor.html @@ -8,13 +8,23 @@ + + - - diff --git a/tests/html/Interface/ChildNode/index.html b/tests/html/Interface/ChildNode/index.html index ed01b5c..e566a2b 100644 --- a/tests/html/Interface/ChildNode/index.html +++ b/tests/html/Interface/ChildNode/index.html @@ -8,6 +8,20 @@ + + + + + + diff --git a/tests/html/Node/cloneNode.html b/tests/html/Node/cloneNode.html index 4265b47..e461a95 100644 --- a/tests/html/Node/cloneNode.html +++ b/tests/html/Node/cloneNode.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/Node/insertBefore.html b/tests/html/Node/insertBefore.html index 00f30de..849728a 100644 --- a/tests/html/Node/insertBefore.html +++ b/tests/html/Node/insertBefore.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/Node/removeChild.html b/tests/html/Node/removeChild.html index c8c5727..8f5a845 100644 --- a/tests/html/Node/removeChild.html +++ b/tests/html/Node/removeChild.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/Node/replaceChild.html b/tests/html/Node/replaceChild.html index 3a65764..4d7f449 100644 --- a/tests/html/Node/replaceChild.html +++ b/tests/html/Node/replaceChild.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/async-import.html b/tests/html/async-import.html index b87f43c..141b273 100644 --- a/tests/html/async-import.html +++ b/tests/html/async-import.html @@ -6,4 +6,4 @@ this.dependencyMet = window.dependencySatisfied; } }); - \ No newline at end of file + diff --git a/tests/html/polyfillWrapFlushCallback/defaultSyncFlush.html b/tests/html/polyfillWrapFlushCallback/defaultSyncFlush.html index 56fd9c5..ac716fa 100644 --- a/tests/html/polyfillWrapFlushCallback/defaultSyncFlush.html +++ b/tests/html/polyfillWrapFlushCallback/defaultSyncFlush.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/polyfillWrapFlushCallback/defineDoesNotWalk.html b/tests/html/polyfillWrapFlushCallback/defineDoesNotWalk.html index ba12bfd..b84b119 100644 --- a/tests/html/polyfillWrapFlushCallback/defineDoesNotWalk.html +++ b/tests/html/polyfillWrapFlushCallback/defineDoesNotWalk.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/polyfillWrapFlushCallback/flushCallbackIsCalled.html b/tests/html/polyfillWrapFlushCallback/flushCallbackIsCalled.html index 632a768..d9d6444 100644 --- a/tests/html/polyfillWrapFlushCallback/flushCallbackIsCalled.html +++ b/tests/html/polyfillWrapFlushCallback/flushCallbackIsCalled.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/polyfillWrapFlushCallback/imperativelyCreatedBeforeFlush.html b/tests/html/polyfillWrapFlushCallback/imperativelyCreatedBeforeFlush.html index 0ac5f31..a3fce28 100644 --- a/tests/html/polyfillWrapFlushCallback/imperativelyCreatedBeforeFlush.html +++ b/tests/html/polyfillWrapFlushCallback/imperativelyCreatedBeforeFlush.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/polyfillWrapFlushCallback/multipleDefineCalls.html b/tests/html/polyfillWrapFlushCallback/multipleDefineCalls.html index 0df1f86..3583528 100644 --- a/tests/html/polyfillWrapFlushCallback/multipleDefineCalls.html +++ b/tests/html/polyfillWrapFlushCallback/multipleDefineCalls.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/polyfillWrapFlushCallback/multipleFlushCallbacks_blocking.html b/tests/html/polyfillWrapFlushCallback/multipleFlushCallbacks_blocking.html index 4ce2c0a..95eec0f 100644 --- a/tests/html/polyfillWrapFlushCallback/multipleFlushCallbacks_blocking.html +++ b/tests/html/polyfillWrapFlushCallback/multipleFlushCallbacks_blocking.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/polyfillWrapFlushCallback/multipleFlushCallbacks_ordering.html b/tests/html/polyfillWrapFlushCallback/multipleFlushCallbacks_ordering.html index 3922616..7452c2c 100644 --- a/tests/html/polyfillWrapFlushCallback/multipleFlushCallbacks_ordering.html +++ b/tests/html/polyfillWrapFlushCallback/multipleFlushCallbacks_ordering.html @@ -8,6 +8,20 @@ + + diff --git a/tests/html/polyfillWrapFlushCallback/upgradeInDefineCallOrder.html b/tests/html/polyfillWrapFlushCallback/upgradeInDefineCallOrder.html index 5098a57..cec3041 100644 --- a/tests/html/polyfillWrapFlushCallback/upgradeInDefineCallOrder.html +++ b/tests/html/polyfillWrapFlushCallback/upgradeInDefineCallOrder.html @@ -8,6 +8,20 @@ + + @@ -40,39 +54,41 @@ test('When a flush callback is installed and multiple calls to define are ' + 'made calling the flush callback causes elements to upgrade in define-call ' + 'order and then document order.', function() { - let flush = undefined; - customElements.polyfillWrapFlushCallback(fn => { - flush = fn; - }); + PatchMonitor.runGuarded(function() { + let flush = undefined; + customElements.polyfillWrapFlushCallback(fn => { + flush = fn; + }); - const upgradeLog = []; - class LogIDOnConstruct extends HTMLElement { - constructor() { - super(); - upgradeLog.push(this.id); + const upgradeLog = []; + class LogIDOnConstruct extends HTMLElement { + constructor() { + super(); + upgradeLog.push(this.id); + } } - } - customElements.define('custom-element-0', class extends LogIDOnConstruct {}); - customElements.define('custom-element-1', class extends LogIDOnConstruct {}); - customElements.define('custom-element-2', class extends LogIDOnConstruct {}); + customElements.define('custom-element-0', class extends LogIDOnConstruct {}); + customElements.define('custom-element-1', class extends LogIDOnConstruct {}); + customElements.define('custom-element-2', class extends LogIDOnConstruct {}); - assert.deepEqual(upgradeLog, []); + assert.deepEqual(upgradeLog, []); - flush(); + flush(); - assert.deepEqual(upgradeLog, [ - "elt_0_0", - "elt_0_1", - "elt_0_2", - "elt_1_0", - "elt_1_1", - "elt_1_2", - "elt_2_0", - "elt_2_1", - "elt_2_2", - "elt_2_3", - ]); + assert.deepEqual(upgradeLog, [ + "elt_0_0", + "elt_0_1", + "elt_0_2", + "elt_1_0", + "elt_1_1", + "elt_1_2", + "elt_2_0", + "elt_2_1", + "elt_2_2", + "elt_2_3", + ]); + }); }); diff --git a/tests/html/polyfillWrapFlushCallback/whenDefined_after.html b/tests/html/polyfillWrapFlushCallback/whenDefined_after.html index 855dad2..32a2515 100644 --- a/tests/html/polyfillWrapFlushCallback/whenDefined_after.html +++ b/tests/html/polyfillWrapFlushCallback/whenDefined_after.html @@ -8,6 +8,20 @@ + + @@ -23,39 +37,41 @@ */ test('`whenDefined` called after `define` does not resolve until its local name has flushed.', function(done) { - const element = document.querySelector('custom-element'); + PatchMonitor.runGuarded(function() { + const element = document.querySelector('custom-element'); - let flushFn = undefined; - let resolved = false; + let flushFn = undefined; + let resolved = false; - customElements.polyfillWrapFlushCallback(flush => flushFn = flush); + customElements.polyfillWrapFlushCallback(flush => flushFn = flush); - customElements.define('custom-element', class extends HTMLElement { - constructor() { - super(); - this.upgraded = true; - if (resolved) { - done(new Error("`whenDefined` promise was resolved before upgrade!")); + customElements.define('custom-element', class extends HTMLElement { + constructor() { + super(); + this.upgraded = true; + if (resolved) { + done(new Error("`whenDefined` promise was resolved before upgrade!")); + } } - } - }); + }); - assert(!element.upgraded); + assert(!element.upgraded); - const promise = customElements.whenDefined('custom-element'); - promise.then(function() { - resolved = true; - assert(element.upgraded); - done(); - }).catch(function(err) { - done(err); - }); + const promise = customElements.whenDefined('custom-element'); + promise.then(function() { + resolved = true; + assert(element.upgraded); + done(); + }).catch(function(err) { + done(err); + }); - assert(!element.upgraded); + assert(!element.upgraded); - flushFn(); + flushFn(); - assert(element.upgraded); + assert(element.upgraded); + }); }); diff --git a/tests/html/polyfillWrapFlushCallback/whenDefined_before.html b/tests/html/polyfillWrapFlushCallback/whenDefined_before.html index 460272a..665fbcc 100644 --- a/tests/html/polyfillWrapFlushCallback/whenDefined_before.html +++ b/tests/html/polyfillWrapFlushCallback/whenDefined_before.html @@ -8,6 +8,20 @@ + + @@ -23,39 +37,41 @@ */ test('`whenDefined` called before `define` does not resolve until its local name has flushed.', function(done) { - const element = document.querySelector('custom-element'); + PatchMonitor.runGuarded(function() { + const element = document.querySelector('custom-element'); - let flushFn = undefined; - let resolved = false; + let flushFn = undefined; + let resolved = false; - customElements.polyfillWrapFlushCallback(flush => flushFn = flush); + customElements.polyfillWrapFlushCallback(flush => flushFn = flush); - const promise = customElements.whenDefined('custom-element'); - promise.then(function() { - resolved = true; - assert(element.upgraded); - done(); - }).catch(function(err) { - done(err); - }); + const promise = customElements.whenDefined('custom-element'); + promise.then(function() { + resolved = true; + assert(element.upgraded); + done(); + }).catch(function(err) { + done(err); + }); - assert(!element.upgraded); + assert(!element.upgraded); - customElements.define('custom-element', class extends HTMLElement { - constructor() { - super(); - this.upgraded = true; - if (resolved) { - done(new Error("`whenDefined` promise was resolved before upgrade!")); + customElements.define('custom-element', class extends HTMLElement { + constructor() { + super(); + this.upgraded = true; + if (resolved) { + done(new Error("`whenDefined` promise was resolved before upgrade!")); + } } - } - }); + }); - assert(!element.upgraded); + assert(!element.upgraded); - flushFn(); + flushFn(); - assert(element.upgraded); + assert(element.upgraded); + }); });