From dabd417e5917d4ae03d11b1f679e99296483967f Mon Sep 17 00:00:00 2001 From: Justin Fagnani Date: Tue, 22 Mar 2016 10:57:45 -0700 Subject: [PATCH] Implement lifecycle callbacks and mutation based upgrades. --- src/CustomElements/v1/CustomElements.js | 170 +++++- tests/CustomElements/v1/js/babel.js | 4 +- tests/CustomElements/v1/js/customElements.js | 541 +++++++++---------- 3 files changed, 394 insertions(+), 321 deletions(-) diff --git a/src/CustomElements/v1/CustomElements.js b/src/CustomElements/v1/CustomElements.js index 259e59d..5fb509c 100644 --- a/src/CustomElements/v1/CustomElements.js +++ b/src/CustomElements/v1/CustomElements.js @@ -29,6 +29,20 @@ window.CustomElements = {}; + function observeRoot(root) { + if (!root.__observer) { + var observer = new MutationObserver(handleMutations); + observer.observe(root, {childList: true, subtree: true}); + root.__observer = observer; + } + return root.__observer; + } + var _observer = observeRoot(document); + + CustomElements.flush = function() { + handleMutations(_observer.takeRecords()); + }; + var _newInstance; var _newTagName; @@ -53,9 +67,14 @@ return i; } if (_newTagName) { - var tagName = _newTagName; + var tagName = _newTagName.toLowerCase(); _newTagName = null; - return document.createElement(tagName); + var element = rawCreateElement(tagName); + var definition = registry.get(tagName); + if (definition) { + _upgradeElement(element, definition, false); + return element; + } } throw new Error('unknown constructor'); } @@ -67,6 +86,26 @@ value: HTMLElement, }); + var rawCreateElement = document.createElement.bind(document); + document.createElement = function(tagName) { + var element = rawCreateElement(tagName); + var definition = registry.get(tagName.toLowerCase()); + if (definition) { + _upgradeElement(element, definition, true); + } + return element; + }; + + var HTMLNS = 'http://www.w3.org/1999/xhtml'; + var _origCreateElementNS = document.createElementNS; + document.createElementNS = function(namespaceURI, qualifiedName) { + if (namespaceURI === 'http://www.w3.org/1999/xhtml') { + return document.createElement(qualifiedName); + } else { + return _origCreateElementNS(namespaceURI, qualifiedName); + } + }; + // @type {Map} var registry = new Map(); @@ -144,15 +183,15 @@ // 5.1.14 var attachedCallback = prototype.attachedCallback; // 5.1.15 - checkCallback(attachedCallback); + checkCallback(attachedCallback, localName, 'attachedCallback'); // 5.1.16 var detachedCallback = prototype.detachedCallback; // 5.1.17 - checkCallback(detachedCallback); + checkCallback(detachedCallback, localName, 'detachedCallback'); // 5.1.18 var attributeChangedCallback = prototype.attributeChangedCallback; // 5.1.19 - checkCallback(attributeChangedCallback); + checkCallback(attributeChangedCallback, localName, 'attributeChangedCallback'); // 5.1.20 // @type {Definition} @@ -163,6 +202,7 @@ attachedCallback: attachedCallback, detachedCallback: detachedCallback, attributeChangedCallback: attributeChangedCallback, + observedAttributes: observedAttributes, }; // 5.1.21 @@ -174,20 +214,6 @@ scheduleUpgrade(); } - var _origCreateElement = document.createElement; - document.createElement = function(tagName) { - var instance = _origCreateElement.call(document, tagName); - var registration = registry.get(tagName); - if (registration) { - var prototype = registration.constructor.prototype; - Object.setPrototypeOf(instance, prototype); - setNewInstance(instance); - new (registration.constructor)(); - console.assert(_newInstance == null); - } - return instance; - }; - function scheduleUpgrade() { if (upgradeTask !== null) { return @@ -213,13 +239,115 @@ } } + var attributeObserver = new MutationObserver(handleAttributeChange); + function handleAttributeChange(mutations) { + console.log('handleAttributeChange', arguments); + for (var i = 0; i < mutations.length; i++) { + var mutation = mutations[i]; + if (mutation.type === 'attributes') { + var name = mutation.attributeName; + var oldValue = mutation.oldValue; + var target = mutation.target; + var newValue = target.getAttribute(name); + var namespace = mutation.attributeNamespace; + target.attributeChangedCallback(name, oldValue, newValue, namespace); + } + } + } + + function _upgradeElement(element, definition, callConstructor) { + var prototype = definition.constructor.prototype; + Object.setPrototypeOf(element, prototype); + if (callConstructor) { + setNewInstance(element); + element.__upgraded = true; + new (definition.constructor)(); + console.assert(_newInstance == null); + } + if (definition.attributeChangedCallback && definition.observedAttributes.length > 0) { + attributeObserver.observe(element, { + attributes: true, + attributeOldValue: true, + attributeFilter: definition.observedAttributes, + }); + } + } + function checkCallback(callback, elementName, calllbackName) { - if (callback !== undefined && !typeof callback !== 'function') { - throw new Error(`TypeError: ${elementName} '$[calllbackName]' is not a Function`); + if (callback !== undefined && typeof callback !== 'function') { + console.warn(typeof callback); + throw new Error(`TypeError: ${elementName} '${calllbackName}' is not a Function`); } } function isReservedTag(name) { return reservedTagList.indexOf(name) !== -1; } + + var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); + + function handleMutations(mutations) { + // console.log('handleMutations', mutations); + for (var i = 0; i < mutations.length; i++) { + var mutation = mutations[i]; + if (mutation.type === 'childList') { + addNodes(mutation.addedNodes); + removeNodes(mutation.removedNodes); + } + } + } + + function addNodes(nodeList) { + for (var i = 0; i < nodeList.length; i++) { + var root = nodeList[i]; + var walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT); + do { + var node = walker.currentNode; + var definition = registry.get(node.localName); + if (!definition) { + continue; + } + if (!node.__upgraded) { + _upgradeElement(node, definition, true); + } + if (node.__upgraded && !node.__attached) { + node.__attached = true; + var definition = registry.get(node.localName); + if (definition && definition.attachedCallback) { + definition.attachedCallback.call(node); + } + } + } while (walker.nextNode()) + } + } + + function removeNodes(nodeList) { + for (var i = 0; i < nodeList.length; i++) { + var root = nodeList[i]; + var walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT); + do { + var node = walker.currentNode; + if (node.__upgraded && node.__attached) { + node.__attached = false; + var definition = registry.get(node.localName); + if (definition && definition.detachedCallback) { + definition.detachedCallback.call(node); + } + } + } while (walker.nextNode()) + } + } + + // recurse up the tree to check if an element is actually in the main document. + function inDocument(element) { + var p = element; + // var doc = window.wrap(document); + var doc = document; + while (p = p.parentNode || ((p.nodeType === Node.DOCUMENT_FRAGMENT_NODE) && p.host)) { + if (p === doc) { + return true; + } + } + return false; + } })(); diff --git a/tests/CustomElements/v1/js/babel.js b/tests/CustomElements/v1/js/babel.js index 11f0e91..6084b94 100644 --- a/tests/CustomElements/v1/js/babel.js +++ b/tests/CustomElements/v1/js/babel.js @@ -12,7 +12,7 @@ suite('babel', function() { // Fails because the XTypescript constructor does not return the result of // the super call. See: https://github.com/Microsoft/TypeScript/issues/7574 - test('document.defineElement create typescript generated ES5 via new', function() { + test('document.defineElement create babel generated ES5 via new', function() { 'use strict'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } @@ -45,7 +45,7 @@ suite('babel', function() { assert.instanceOf(e, XBabel); }); - test('document.defineElement create typescript generated ES5 via createElement', function() { + test('document.defineElement create babel generated ES5 via createElement', function() { 'use strict'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } diff --git a/tests/CustomElements/v1/js/customElements.js b/tests/CustomElements/v1/js/customElements.js index b689463..f2d7a2b 100644 --- a/tests/CustomElements/v1/js/customElements.js +++ b/tests/CustomElements/v1/js/customElements.js @@ -128,7 +128,6 @@ suite('customElements', function() { document.defineElement('x-foo-es5', XFooES5); // create an instance via new var xfoo = new XFooES5(); - console.log(xfoo); // test localName assert.equal(xfoo.localName, 'x-foo-es5'); // test instanceof @@ -197,276 +196,212 @@ suite('customElements', function() { document.defineElement('x-bar-es5', XBarES5); // create an instance via createElement var xbar = document.createElement('x-bar-es5'); - console.log(xbar); // test localName assert.equal(xbar.localName, 'x-bar-es5'); // test instanceof assert.instanceOf(xbar, XBarES5); }); - // test('document.registerElement create via createElementNS', function() { - // // create an instance via createElementNS - // var xfoo = document.createElementNS(HTMLNS, 'x-foo2'); - // // test localName - // assert.equal(xfoo.localName, 'x-foo2'); - // // attach content - // xfoo.textContent = '[x-foo2]'; - // // test textContent - // assert.equal(xfoo.textContent, '[x-foo2]'); - // }); - // - // test('document.registerElement treats names as case insensitive', function() { - // var proto = {prototype: Object.create(HTMLElement.prototype)}; - // proto.prototype.isXCase = true; - // var XCase = document.registerElement('X-CASE', proto); - // // createElement - // var x = document.createElement('X-CASE'); - // assert.equal(x.isXCase, true); - // x = document.createElement('x-case'); - // assert.equal(x.isXCase, true); - // // createElementNS - // // NOTE: createElementNS is case sensitive, disable tests - // // x = document.createElementNS(HTMLNS, 'X-CASE'); - // // assert.equal(x.isXCase, true); - // // x = document.createElementNS(HTMLNS, 'x-case'); - // // assert.equal(x.isXCase, true); - // // upgrade - // work.innerHTML = ''; - // CustomElements.takeRecords(); - // assert.equal(work.firstChild.isXCase, true); - // assert.equal(work.firstChild.nextSibling.isXCase, true); - // }); - // - // test('document.registerElement create multiple instances', function() { - // var XFooPrototype = Object.create(HTMLElement.prototype); - // XFooPrototype.bluate = function() { - // this.color = 'lightblue'; - // }; - // var XFoo = document.registerElement('x-foo3', { - // prototype: XFooPrototype - // }); - // // create an instance - // var xfoo1 = new XFoo(); - // // create another instance - // var xfoo2 = new XFoo(); - // // test textContent - // xfoo1.textContent = '[x-foo1]'; - // xfoo2.textContent = '[x-foo2]'; - // assert.equal(xfoo1.textContent, '[x-foo1]'); - // assert.equal(xfoo2.textContent, '[x-foo2]'); - // // test bluate - // xfoo1.bluate(); - // assert.equal(xfoo1.color, 'lightblue'); - // assert.isUndefined(xfoo2.color); - // }); - // - // test('document.registerElement extend native element', function() { - // // test native element extension - // var XBarPrototype = Object.create(HTMLButtonElement.prototype); - // var XBar = document.registerElement('x-bar', { - // prototype: XBarPrototype, - // extends: 'button' - // }); - // var xbar = new XBar(); - // work.appendChild(xbar).textContent = 'x-bar'; - // xbar = work.querySelector('button[is=x-bar]'); - // assert(xbar); - // assert.equal(xbar.textContent, 'x-bar'); - // // test extension of native element extension - // var XBarBarPrototype = Object.create(XBarPrototype); - // var XBarBar = document.registerElement('x-barbar', { - // prototype: XBarBarPrototype, - // extends: 'button' - // }); - // var xbarbar = new XBarBar(); - // work.appendChild(xbarbar).textContent = 'x-barbar'; - // xbarbar = work.querySelector('button[is=x-barbar]'); - // assert(xbarbar); - // assert.equal(xbarbar.textContent, 'x-barbar'); - // // test extension^3 - // var XBarBarBarPrototype = Object.create(XBarBarPrototype); - // var XBarBarBar = document.registerElement('x-barbarbar', { - // prototype: XBarBarBarPrototype, - // extends: 'button' - // }); - // var xbarbarbar = new XBarBarBar(); - // work.appendChild(xbarbarbar).textContent = 'x-barbarbar'; - // xbarbarbar = work.querySelector('button[is=x-barbarbar]'); - // assert(xbarbarbar); - // assert.equal(xbarbarbar.textContent, 'x-barbarbar'); - // }); - // - // test('document.registerElement with type extension treats names as case insensitive', function() { - // var proto = {prototype: Object.create(HTMLButtonElement.prototype), extends: 'button'}; - // proto.prototype.isXCase = true; - // var XCase = document.registerElement('X-EXTEND-CASE', proto); - // // createElement - // var x = document.createElement('button', 'X-EXTEND-CASE'); - // assert.equal(x.isXCase, true); - // x = document.createElement('button', 'x-extend-case'); - // assert.equal(x.isXCase, true); - // x = document.createElement('BUTTON', 'X-EXTEND-CASE'); - // assert.equal(x.isXCase, true); - // x = document.createElement('BUTTON', 'x-extend-case'); - // assert.equal(x.isXCase, true); - // // upgrade - // work.innerHTML = ''; - // CustomElements.takeRecords(); - // assert.equal(work.firstChild.isXCase, true); - // assert.equal(work.firstChild.nextSibling.isXCase, true); - // }); - // - // test('document.registerElement createdCallback in prototype', function() { - // var XBooPrototype = Object.create(HTMLElement.prototype); - // XBooPrototype.createdCallback = function() { - // this.style.fontStyle = 'italic'; - // } - // var XBoo = document.registerElement('x-boo', { - // prototype: XBooPrototype - // }); - // var xboo = new XBoo(); - // assert.equal(xboo.style.fontStyle, 'italic'); - // // - // var XBooBooPrototype = Object.create(XBooPrototype); - // XBooBooPrototype.createdCallback = function() { - // XBoo.prototype.createdCallback.call(this); - // this.style.fontSize = '32pt'; - // }; - // var XBooBoo = document.registerElement('x-booboo', { - // prototype: XBooBooPrototype - // }); - // var xbooboo = new XBooBoo(); - // assert.equal(xbooboo.style.fontStyle, 'italic'); - // assert.equal(xbooboo.style.fontSize, '32pt'); - // }); - // - // test('document.registerElement [created|attached|detached]Callbacks in prototype', function(done) { - // var ready, inserted, removed; - // var XBooPrototype = Object.create(HTMLElement.prototype); - // XBooPrototype.createdCallback = function() { - // ready = true; - // } - // XBooPrototype.attachedCallback = function() { - // inserted = true; - // } - // XBooPrototype.detachedCallback = function() { - // removed = true; - // } - // var XBoo = document.registerElement('x-boo-ir', { - // prototype: XBooPrototype - // }); - // var xboo = new XBoo(); - // assert(ready, 'ready must be true [XBoo]'); - // assert(!inserted, 'inserted must be false [XBoo]'); - // assert(!removed, 'removed must be false [XBoo]'); - // work.appendChild(xboo); - // CustomElements.takeRecords(); - // assert(inserted, 'inserted must be true [XBoo]'); - // work.removeChild(xboo); - // CustomElements.takeRecords(); - // assert(removed, 'removed must be true [XBoo]'); - // // - // ready = inserted = removed = false; - // var XBooBooPrototype = Object.create(XBooPrototype); - // XBooBooPrototype.createdCallback = function() { - // XBoo.prototype.createdCallback.call(this); - // }; - // XBooBooPrototype.attachedCallback = function() { - // XBoo.prototype.attachedCallback.call(this); - // }; - // XBooBooPrototype.detachedCallback = function() { - // XBoo.prototype.detachedCallback.call(this); - // }; - // var XBooBoo = document.registerElement('x-booboo-ir', { - // prototype: XBooBooPrototype - // }); - // var xbooboo = new XBooBoo(); - // assert(ready, 'ready must be true [XBooBoo]'); - // assert(!inserted, 'inserted must be false [XBooBoo]'); - // assert(!removed, 'removed must be false [XBooBoo]'); - // work.appendChild(xbooboo); - // CustomElements.takeRecords(); - // assert(inserted, 'inserted must be true [XBooBoo]'); - // work.removeChild(xbooboo); - // CustomElements.takeRecords(); - // assert(removed, 'removed must be true [XBooBoo]'); - // done(); - // }); - // - // test('document.registerElement attributeChangedCallback in prototype', function(done) { - // var XBooPrototype = Object.create(HTMLElement.prototype); - // XBooPrototype.attributeChangedCallback = function(inName, inOldValue) { - // if (inName == 'foo' && inOldValue=='bar' - // && this.attributes.foo.value == 'zot') { - // done(); - // } - // } - // var XBoo = document.registerElement('x-boo-acp', { - // prototype: XBooPrototype - // }); - // var xboo = new XBoo(); - // xboo.setAttribute('foo', 'bar'); - // xboo.setAttribute('foo', 'zot'); - // }); - // - // test('document.registerElement attachedCallbacks in prototype', function(done) { - // var inserted = 0; - // var XBooPrototype = Object.create(HTMLElement.prototype); - // XBooPrototype.attachedCallback = function() { - // inserted++; - // }; - // var XBoo = document.registerElement('x-boo-at', { - // prototype: XBooPrototype - // }); - // var xboo = new XBoo(); - // assert.equal(inserted, 0, 'inserted must be 0'); - // work.appendChild(xboo); - // CustomElements.takeRecords(); - // assert.equal(inserted, 1, 'inserted must be 1'); - // work.removeChild(xboo); - // CustomElements.takeRecords(); - // assert(!xboo.parentNode); - // work.appendChild(xboo); - // CustomElements.takeRecords(); - // assert.equal(inserted, 2, 'inserted must be 2'); - // done(); - // }); - // - // test('document.registerElement detachedCallbacks in prototype', function(done) { - // var ready, inserted, removed; - // var XBooPrototype = Object.create(HTMLElement.prototype); - // XBooPrototype.detachedCallback = function() { - // removed = true; - // } - // var XBoo = document.registerElement('x-boo-ir2', { - // prototype: XBooPrototype - // }); - // var xboo = new XBoo(); - // assert(!removed, 'removed must be false [XBoo]'); - // work.appendChild(xboo); - // CustomElements.takeRecords(); - // work.removeChild(xboo); - // CustomElements.takeRecords(); - // assert(removed, 'removed must be true [XBoo]'); - // // - // ready = inserted = removed = false; - // var XBooBooPrototype = Object.create(XBooPrototype); - // XBooBooPrototype.detachedCallback = function() { - // XBoo.prototype.detachedCallback.call(this); - // }; - // var XBooBoo = document.registerElement('x-booboo-ir2', { - // prototype: XBooBooPrototype - // }); - // var xbooboo = new XBooBoo(); - // assert(!removed, 'removed must be false [XBooBoo]'); - // work.appendChild(xbooboo); - // CustomElements.takeRecords(); - // work.removeChild(xbooboo); - // CustomElements.takeRecords(); - // assert(removed, 'removed must be true [XBooBoo]'); - // done(); - // }); - // + test('document.defineElement create via createElementNS', function() { + class XFoo3 extends HTMLElement { + constructor() { + CustomElements.setCurrentTag('x-foo3'); + super(); + } + } + // register x-foo + document.defineElement('x-foo3', XFoo3); + // create an instance via createElementNS + var xfoo = document.createElementNS(HTMLNS, 'x-foo3'); + // test instanceof + assert.instanceOf(xfoo, XFoo3); + // test localName + assert.equal(xfoo.localName, 'x-foo3'); + }); + + test('document.defineElement treats names as case insensitive', function() { + class XCase extends HTMLElement { + constructor() { + CustomElements.setCurrentTag('x-case'); + super(); + this.isXCase = true; + } + } + document.defineElement('X-CASE', XCase); + // createElement + var x = document.createElement('X-CASE'); + assert.equal(x.isXCase, true); + x = document.createElement('x-case'); + assert.equal(x.isXCase, true); + // createElementNS + // NOTE: createElementNS is case sensitive, disable tests + // x = document.createElementNS(HTMLNS, 'X-CASE'); + // assert.equal(x.isXCase, true); + // x = document.createElementNS(HTMLNS, 'x-case'); + // assert.equal(x.isXCase, true); + // upgrade + + // TODO: uncomment when upgrades implemented + // work.innerHTML = ''; + // CustomElements.takeRecords(); + // assert.equal(work.firstChild.isXCase, true); + // assert.equal(work.firstChild.nextSibling.isXCase, true); + }); + + test('document.defineElement create multiple instances', function() { + // create an instance + var xfoo1 = document.createElement('x-foo'); + // create another instance + var xfoo2 = document.createElement('x-foo'); + + assert.notStrictEqual(xfoo1, xfoo2); + // test textContent + xfoo1.textContent = '[x-foo1]'; + xfoo2.textContent = '[x-foo2]'; + assert.equal(xfoo1.textContent, '[x-foo1]'); + assert.equal(xfoo2.textContent, '[x-foo2]'); + }); + + test('document.defineElement calls constructor only once', function() { + var count = 0; + class XConstructor extends HTMLElement { + constructor() { + CustomElements.setCurrentTag('x-constructor'); + super(); + count++; + } + } + document.defineElement('x-constructor', XConstructor); + var xconstructor = new XConstructor(); + assert.equal(count, 1); + }); + + test('document.defineElement [attached|detached]Callbacks', function(done) { + class XCallbacks extends HTMLElement { + constructor() { + CustomElements.setCurrentTag('x-callbacks'); + super(); + this.attached = false; + this.detached = false; + } + + attachedCallback() { + this.attached = true; + } + + detachedCallback() { + this.detached = true; + } + } + document.defineElement('x-callbacks', XCallbacks); + var e = new XCallbacks(); + assert.isFalse(e.attached); + assert.isFalse(e.detached); + + work.appendChild(e); + CustomElements.flush(); + assert.isTrue(e.attached); + assert.isFalse(e.detached); + + work.removeChild(e); + CustomElements.flush(); + assert.isTrue(e.attached); + assert.isTrue(e.detached); + + done(); + }); + + + test('document.defineElement attributeChangedCallback in prototype', function(done) { + class XBoo extends HTMLElement { + static get observedAttributes() { + return ['foo']; + } + + constructor() { + CustomElements.setCurrentTag('x-boo-acp'); + super(); + } + attributeChangedCallback(inName, inOldValue) { + if (inName == 'foo' && inOldValue=='bar' + && this.attributes.foo.value == 'zot') { + done(); + } + } + } + document.defineElement('x-boo-acp', XBoo); + var xboo = new XBoo(); + xboo.setAttribute('foo', 'bar'); + xboo.setAttribute('foo', 'zot'); + }); + + test('document.defineElement attachedCallbacks in prototype', function(done) { + var inserted = 0; + class XBoo extends HTMLElement { + constructor() { + CustomElements.setCurrentTag('x-boo-at'); + super(); + } + attachedCallback() { + inserted++; + } + } + document.defineElement('x-boo-at', XBoo); + var xboo = new XBoo(); + assert.equal(inserted, 0, 'inserted must be 0'); + work.appendChild(xboo); + CustomElements.flush(); + assert.equal(inserted, 1, 'inserted must be 1'); + work.removeChild(xboo); + CustomElements.flush(); + assert(!xboo.parentNode); + work.appendChild(xboo); + CustomElements.flush(); + assert.equal(inserted, 2, 'inserted must be 2'); + done(); + }); + + test('document.registerElement detachedCallbacks in prototype', function(done) { + var ready, inserted, removed; + class XBoo extends HTMLElement { + constructor() { + CustomElements.setCurrentTag('x-boo-ir2'); + super(); + } + detachedCallback() { + removed = true; + } + } + document.defineElement('x-boo-ir2', XBoo); + var xboo = new XBoo(); + assert(!removed, 'removed must be false [XBoo]'); + work.appendChild(xboo); + CustomElements.flush(); + work.removeChild(xboo); + CustomElements.flush(); + assert(removed, 'removed must be true [XBoo]'); + + ready = inserted = removed = false; + class XBooBoo extends HTMLElement { + constructor() { + CustomElements.setCurrentTag('x-booboo-ir2'); + super(); + } + detachedCallback() { + removed = true; + } + } + document.defineElement('x-booboo-ir2', XBooBoo); + var xbooboo = new XBooBoo(); + assert(!removed, 'removed must be false [XBooBoo]'); + work.appendChild(xbooboo); + CustomElements.flush(); + work.removeChild(xbooboo); + CustomElements.flush(); + assert(removed, 'removed must be true [XBooBoo]'); + done(); + }); + // test('document.registerElement can use Functions as definitions', function() { // // function used as Custom Element defintion // function A$A() { @@ -484,39 +419,49 @@ suite('customElements', function() { // CustomElements.takeRecords(); // assert.equal(work.firstElementChild.alive, true); // }); - // - // test('node.cloneNode upgrades', function(done) { - // var XBooPrototype = Object.create(HTMLElement.prototype); - // XBooPrototype.createdCallback = function() { - // this.__ready__ = true; - // }; - // var XBoo = document.registerElement('x-boo-clone', { - // prototype: XBooPrototype - // }); - // var xboo = new XBoo(); - // work.appendChild(xboo); - // CustomElements.takeRecords(); - // var xboo2 = xboo.cloneNode(true); - // assert(xboo2.__ready__, 'clone createdCallback must be called'); - // done(); - // }); - // - // test('document.importNode upgrades', function() { - // var XImportPrototype = Object.create(HTMLElement.prototype); - // XImportPrototype.createdCallback = function() { - // this.__ready__ = true; - // }; - // document.registerElement('x-import', { - // prototype: XImportPrototype - // }); - // var frag = document.createDocumentFragment(); - // frag.appendChild(document.createElement('x-import')); - // assert.isTrue(frag.firstChild.__ready__, 'source element upgraded'); - // var imported = document.importNode(frag, true); - // window.imported = imported; - // assert.isTrue(imported.firstChild.__ready__, 'imported element upgraded'); - // }); - // + + test('node.cloneNode does not upgrade until attach', function(done) { + class XBoo extends HTMLElement { + constructor() { + CustomElements.setCurrentTag('x-boo-clone'); + super(); + this.__ready__ = true; + } + } + document.defineElement('x-boo-clone', XBoo); + var xboo = new XBoo(); + work.appendChild(xboo); + CustomElements.flush(); + var xboo2 = xboo.cloneNode(true); + CustomElements.flush(); + assert.isNotOk(xboo2.__ready__, 'clone createdCallback must be called'); + work.appendChild(xboo2); + CustomElements.flush(); + assert.isTrue(xboo2.__ready__, 'clone createdCallback must be called'); + done(); + }); + + test('document.importNode upgrades', function() { + class XImport extends HTMLElement { + constructor() { + CustomElements.setCurrentTag('x-import'); + super(); + this.__ready__ = true; + } + } + document.defineElement('x-import', XImport); + var frag = document.createDocumentFragment(); + frag.appendChild(document.createElement('x-import')); + assert.isTrue(frag.firstChild.__ready__, 'source element upgraded'); + var imported = document.importNode(frag, true); + window.imported = imported; + var importedEl = imported.firstChild; + assert.isNotOk(importedEl.__ready__, 'imported element upgraded'); + work.appendChild(imported); + CustomElements.flush(); + assert.isOk(importedEl.__ready__, 'imported element upgraded'); + }); + // test('entered left apply to view', function() { // var invocations = []; // var elementProto = Object.create(HTMLElement.prototype);