From 7954f9336fe2b9c972331f54ffcd825f573ca95f Mon Sep 17 00:00:00 2001 From: Kevin Schaaf Date: Tue, 14 Jul 2015 18:58:32 -0700 Subject: [PATCH] Add Polymer.instanceof & isInstance. Fixes #2083. --- polymer-micro.html | 2 -- polymer-mini.html | 4 +--- polymer.html | 2 -- src/lib/base.html | 17 +++++++++++++++++ src/lib/dom-api.html | 16 ++++++++-------- src/lib/polymer-bootstrap.html | 9 ++++++++- src/lib/template/dom-bind.html | 1 - src/micro/extends.html | 6 ------ src/standard/x-styling.html | 8 +++++--- test/unit/micro.html | 29 +++++++++++++++++++++++++++-- test/unit/polymer-dom.js | 4 ++-- 11 files changed, 68 insertions(+), 30 deletions(-) diff --git a/polymer-micro.html b/polymer-micro.html index 9b4d710c6c..ffbc2aac3d 100644 --- a/polymer-micro.html +++ b/polymer-micro.html @@ -31,8 +31,6 @@ this._prepAttributes(); // shared behaviors this._prepBehaviors(); - // inheritance - this._prepExtends(); // factory this._prepConstructor(); }, diff --git a/polymer-mini.html b/polymer-mini.html index 8290035cb1..98e2e45d65 100644 --- a/polymer-mini.html +++ b/polymer-mini.html @@ -28,9 +28,7 @@ this._prepAttributes(); // shared behaviors this._prepBehaviors(); - // inheritance - this._prepExtends(); - // factory + // factory this._prepConstructor(); // template this._prepTemplate(); diff --git a/polymer.html b/polymer.html index 596a977214..f94778dd49 100644 --- a/polymer.html +++ b/polymer.html @@ -30,8 +30,6 @@ this._prepIs(); // attributes this._prepAttributes(); - // inheritance - this._prepExtends(); // factory this._prepConstructor(); // template diff --git a/src/lib/base.html b/src/lib/base.html index df2e205212..4dfa75837d 100644 --- a/src/lib/base.html +++ b/src/lib/base.html @@ -11,6 +11,11 @@ Polymer.Base = { + // Used for `isInstance` type checking; cannot use `instanceof` because + // there is no common Polymer.Base in the prototype chain between type + // extensions and normal custom elements + __isPolymerInstance__: true, + // pluggable features // `this` context is a prototype, not an instance _addFeature: function(feature) { @@ -118,6 +123,18 @@ Polymer.Base = Polymer.Base.chainObject(Polymer.Base, HTMLElement.prototype); + if (window.CustomElements) { + Polymer.instanceof = CustomElements.instanceof; + } else { + Polymer.instanceof = function(obj, ctor) { + return obj instanceof ctor; + }; + } + + Polymer.isInstance = function(obj) { + return Boolean(obj && obj.__isPolymerInstance__); + }; + // TODO(sjmiles): ad hoc telemetry Polymer.telemetry.instanceCount = 0; diff --git a/src/lib/dom-api.html b/src/lib/dom-api.html index b2da9d362d..fdd3fc3607 100644 --- a/src/lib/dom-api.html +++ b/src/lib/dom-api.html @@ -177,7 +177,7 @@ var fragContent = (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) && !node.__noContent && Polymer.dom(node).querySelector(CONTENT); var wrappedContent = fragContent && - (Polymer.dom(fragContent).parentNode.nodeType !== + (Polymer.dom(fragContent).parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE); var hasContent = fragContent || (node.localName === CONTENT); // There are 2 possible cases where a distribution may need to occur: @@ -231,8 +231,8 @@ return parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot); }, - // NOTE: if `ensureComposedRemoval` is true then the node should be - // removed from its composed parent. + // NOTE: if `ensureComposedRemoval` is true then the node should be + // removed from its composed parent. _removeNodeFromHost: function(node, ensureComposedRemoval) { var hostNeedsDist; var root; @@ -459,7 +459,7 @@ importNode: function(externalNode, deep) { // for convenience use this node's ownerDoc if the node isn't a document - var doc = this.node instanceof HTMLDocument ? this.node : + var doc = this.node instanceof Document ? this.node : this.node.ownerDocument; var n = nativeImportNode.call(doc, externalNode, false); if (deep) { @@ -506,7 +506,7 @@ this.domApi._distributeParent(); }, contains: function() { - return this.node.classList.contains.apply(this.node.classList, + return this.node.classList.contains.apply(this.node.classList, arguments); } } @@ -682,7 +682,7 @@ } DomApi.prototype.importNode = function(externalNode, deep) { - var doc = this.node instanceof HTMLDocument ? this.node : + var doc = this.node instanceof Document ? this.node : this.node.ownerDocument; return doc.importNode(externalNode, deep); } @@ -776,8 +776,8 @@ function getLightChildren(node) { var children = node._lightChildren; - // TODO(sorvell): it's more correct to use _composedChildren instead of - // childNodes here but any trivial failure to use Polymer.dom + // TODO(sorvell): it's more correct to use _composedChildren instead of + // childNodes here but any trivial failure to use Polymer.dom // will result in an error so we avoid using _composedChildren return children ? children : node.childNodes; } diff --git a/src/lib/polymer-bootstrap.html b/src/lib/polymer-bootstrap.html index 95654545b7..2fd5f95d92 100644 --- a/src/lib/polymer-bootstrap.html +++ b/src/lib/polymer-bootstrap.html @@ -38,7 +38,14 @@ }; var desugar = function(prototype) { - prototype = Polymer.Base.chainObject(prototype, Polymer.Base); + // Note: need to chain user prorotype with the correct type-extended + // version of Polymer.Base; this is especially important when you can't + // prototype swizzle (e.g. IE10), since CustomElemets uses getPrototypeOf + var base = Polymer.Base; + if (prototype.extends) { + base = Polymer.Base._getExtendedPrototype(prototype.extends); + } + prototype = Polymer.Base.chainObject(prototype, base); prototype.registerCallback(); return prototype.constructor; }; diff --git a/src/lib/template/dom-bind.html b/src/lib/template/dom-bind.html index 0a1a96b341..8f98dd3248 100644 --- a/src/lib/template/dom-bind.html +++ b/src/lib/template/dom-bind.html @@ -72,7 +72,6 @@ }, _registerFeatures: function() { - this._prepExtends(); this._prepConstructor(); }, diff --git a/src/micro/extends.html b/src/micro/extends.html index 065302f78f..705d39f5b7 100644 --- a/src/micro/extends.html +++ b/src/micro/extends.html @@ -47,12 +47,6 @@ Polymer.Base._addFeature({ - _prepExtends: function() { - if (this.extends) { - this.__proto__ = this._getExtendedPrototype(this.extends); - } - }, - _getExtendedPrototype: function(tag) { return this._getExtendedNativePrototype(tag); }, diff --git a/src/standard/x-styling.html b/src/standard/x-styling.html index 42c600a906..98a84e9a05 100644 --- a/src/standard/x-styling.html +++ b/src/standard/x-styling.html @@ -55,7 +55,7 @@ _findStyleHost: function() { var e = this, root; while (root = Polymer.dom(e).getOwnerRoot()) { - if (root.host && root.host._computeStyleProperties) { + if (Polymer.isInstance(root.host)) { return root.host; } e = root.host; @@ -174,8 +174,10 @@ serializeValueToAttribute: function(value, attribute, node) { // override to ensure whenever classes are set, we need to shim them. node = node || this; - if (attribute === 'class') { + if (attribute === 'class' && !nativeShadow) { // host needed to scope styling. + // Under Shady DOM, domHost is safe to use here because we know it + // is a Polymer element var host = node === this ? (this.domHost || this.dataHost) : this; if (host) { value = host._scopeElementClass(node, value); @@ -206,7 +208,7 @@ * been made that affect the values of custom properties. * * @method updateStyles - * @param {Object=} properties Properties object which is mixed into + * @param {Object=} properties Properties object which is mixed into * the element's `customStyle` property. This argument provides a shortcut * for setting `customStyle` and then calling `updateStyles`. */ diff --git a/test/unit/micro.html b/test/unit/micro.html index 56dbcc349f..7d443ea61c 100644 --- a/test/unit/micro.html +++ b/test/unit/micro.html @@ -42,7 +42,22 @@ }); - suite('constructor', function() { + suite('type checking & constructor', function() { + + test('Polymer.isInstance for non-instance', function() { + var el = document.createElement('div'); + assert.isTrue(Polymer.instanceof(el, HTMLElement)); + assert.isTrue(Polymer.instanceof(el, HTMLDivElement)); + assert.isFalse(Polymer.isInstance(el)); + }); + + test('document.createElement', function() { + var MyElement = Polymer({is: 'my-basic'}); + var el = document.createElement('my-basic'); + assert.isTrue(Polymer.instanceof(el, HTMLElement)); + assert.isTrue(Polymer.instanceof(el, MyElement)); + assert.isTrue(Polymer.isInstance(el)); + }); test('normal constructor', function() { var MyElement = Polymer({is: 'my-element'}); @@ -53,6 +68,9 @@ assert.instanceOf(el, MyElement, 'Instance of MyElement'); } assert.instanceOf(el, HTMLElement, 'Instance of HTMLElement'); + assert.isTrue(Polymer.instanceof(el, HTMLElement)); + assert.isTrue(Polymer.instanceof(el, MyElement)); + assert.isTrue(Polymer.isInstance(el)); }); test('type-extension constructor', function() { @@ -62,8 +80,12 @@ if (Object.__proto__) { // instanceof Constructor only supported where proto swizzling is possible assert.instanceOf(el, MyInput, 'Instance of MyInput'); + assert.instanceOf(el, HTMLElement, 'Instance of HTMLInputElement'); } - assert.instanceOf(el, HTMLElement, 'Instance of HTMLInputElement'); + assert.isTrue(Polymer.instanceof(el, HTMLInputElement)); + assert.isTrue(Polymer.instanceof(el, HTMLElement)); + assert.isTrue(Polymer.instanceof(el, MyInput)); + assert.isTrue(Polymer.isInstance(el)); }); test('custom constructor', function() { @@ -81,6 +103,9 @@ assert.instanceOf(el, HTMLElement, 'Instance of HTMLElement'); } assert.equal(el.title, 'my title', 'Argument passed to constructor'); + assert.isTrue(Polymer.instanceof(el, HTMLElement)); + assert.isTrue(Polymer.instanceof(el, MyElement2)); + assert.isTrue(Polymer.isInstance(el)); }); }); diff --git a/test/unit/polymer-dom.js b/test/unit/polymer-dom.js index df0c2daeaa..a677d2a457 100644 --- a/test/unit/polymer-dom.js +++ b/test/unit/polymer-dom.js @@ -531,7 +531,7 @@ suite('Polymer.dom', function() { test('Polymer.dom importNode shallow', function() { var a = document.createElement('div'); a.innerHTML = '12'; - var b = Polymer.dom(document).importNode(Polymer.dom(a).firstElementChild); + var b = Polymer.dom(wrap(document)).importNode(Polymer.dom(a).firstElementChild); Polymer.dom(document.body).appendChild(b); assert.equal(Polymer.dom(b).childNodes.length, 0, 'shallow import has incorrect children'); if (b.shadyRoot) { @@ -542,7 +542,7 @@ suite('Polymer.dom', function() { test('Polymer.dom importNode deep', function() { var a = document.createElement('div'); a.innerHTML = '12'; - var b = Polymer.dom(document).importNode(a, true); + var b = Polymer.dom(wrap(document)).importNode(a, true); Polymer.dom(document.body).appendChild(b); assert.equal(Polymer.dom(b.firstElementChild).childNodes.length, 2, 'deep copy has incorrect children'); if (b.shadyRoot) {