skatejs / web-components

[DEPRECATED] - The frictionless way to use the webcomponents/webcomponentsjs polyfills.
MIT License
17 stars 4 forks source link

fix(package): Bump the webcomponents js version sha #3

Closed jpnelson closed 7 years ago

jpnelson commented 7 years ago

This was needed due to a problem with loading skate in the head - particularly around 's that aren't type import, and so link.import was undefined. It's fixed in the later version.

diff --git a/.travis.yml b/.travis.yml
index bafba95..a4f67bb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,16 +1,15 @@
 language: node_js
-sudo: false
-node_js: 4
+sudo: required
+dist: trusty
+node_js: stable
 addons:
   sauce_connect: true
   firefox: latest
   apt:
     sources:
     - google-chrome
-    - ubuntu-toolchain-r-test
     packages:
     - google-chrome-stable
-    - g++-4.8
 before_script:
 - export PATH=$PWD/node_modules/.bin:$PATH
 script:
@@ -20,4 +19,3 @@ env:
   global:
   - secure: c0kVrjNDtqd06Gyg4Xi3iopr0KCz1k0LbZeL+TCbnyCdmAE7m9FcJASWvM2Zr7d774hTiMSi0Z79SlV6XZhLN2pi4EsbdEpsnVeAXXH/GYzDKgpXbdfD/nQv4n1nMXL6XSaZkAX7WwgmrjzJ9cXQJYV9vNHIBRcGoVRRyCFx9v4=
   - secure: Mo+AVRGUmlDENnZ2GioF5pU62WhyLUMnPlSqzeodZzJoAnwcNr9VnHiRCgQBLnHCZwjbMv6C0vhWopY7lN9w77vlS5vr8MDZKjYT/YRl9jk0+hStJ+diSS9MD+FnNNerXe+V+WA6NYVHno3vdWRqDDMYzCdH/pyLukkuKdMFaAU=
-  - CXX=g++-4.8
diff --git a/bower.json b/bower.json
index 7603c35..925f321 100644
--- a/bower.json
+++ b/bower.json
@@ -1,7 +1,7 @@
 {
   "name": "webcomponentsjs",
   "main": "webcomponents.js",
-  "version": "0.7.21",
+  "version": "0.7.22",
   "homepage": "http://webcomponents.org",
   "authors": [
     "The Polymer Authors"
diff --git a/gulpfile.js b/gulpfile.js
index c0f5977..c671ef0 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -138,7 +138,6 @@ gulp.task('CustomElementsV1', function () {
           warning_level: 'VERBOSE',
           language_in: 'ECMASCRIPT6_STRICT',
           language_out: 'ECMASCRIPT5_STRICT',
-          output_wrapper: '(function(){\n%output%\n}).call(this)',
           externs: 'externs/html5.js',
           js_output_file: 'CustomElementsV1.min.js'
         }))
@@ -146,7 +145,8 @@ gulp.task('CustomElementsV1', function () {
 });

 gulp.task('build', ['webcomponents', 'webcomponents-lite', 'CustomElements',
-  'HTMLImports', 'ShadowDOM', 'copy-bower', 'MutationObserver']);
+  'CustomElementsV1', 'HTMLImports', 'ShadowDOM', 'copy-bower',
+  'MutationObserver']);

 gulp.task('release', function(cb) {
   isRelease = true;
diff --git a/package.json b/package.json
index 3552258..0718dd5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "webcomponents.js",
-  "version": "0.7.21",
+  "version": "0.7.22",
   "description": "webcomponents.js",
   "main": "webcomponents.js",
   "directories": {
diff --git a/src/CustomElements/register.js b/src/CustomElements/register.js
index 3a58bfe..881a1e7 100644
--- a/src/CustomElements/register.js
+++ b/src/CustomElements/register.js
@@ -91,6 +91,10 @@ function register(name, options) {
   }
   // record name
   definition.__name = name.toLowerCase();
+  // ensure extended name is also treated case-insensitively
+  if (definition.extends) {
+    definition.extends = definition.extends.toLowerCase();
+  }
   // ensure a lifecycle object so we don't have to null test it
   definition.lifecycle = definition.lifecycle || {};
   // build a list of ancestral custom elements (for native base detection)
@@ -350,27 +354,6 @@ function wrapDomMethodToForceUpgrade(obj, methodName) {
 wrapDomMethodToForceUpgrade(Node.prototype, 'cloneNode');
 wrapDomMethodToForceUpgrade(document, 'importNode');

-// Patch document.importNode to work around IE11 bug that
-// casues children of a document fragment imported while
-// there is a mutation observer to not have a parentNode (!?!)
-if (isIE) {
-  (function() {
-    var importNode = document.importNode;
-    document.importNode = function() {
-      var n = importNode.apply(document, arguments);
-      // Copy all children to a new document fragment since
-      // this one may be broken
-      if (n.nodeType == n.DOCUMENT_FRAGMENT_NODE) {
-        var f = document.createDocumentFragment();
-        f.appendChild(n);
-        return f;
-      } else {
-        return n;
-      }
-    };
-  })();
-}
-
 // exports
 document.registerElement = register;
 document.createElement = createElement; // override
diff --git a/src/CustomElements/v1/CustomElements.js b/src/CustomElements/v1/CustomElements.js
index aec587f..820c13b 100644
--- a/src/CustomElements/v1/CustomElements.js
+++ b/src/CustomElements/v1/CustomElements.js
@@ -29,6 +29,17 @@ var CustomElementDefinition;
   var doc = document;
   var win = window;

+  if (win.customElements) {
+    if (win.customElements['enableFlush']) {
+      win.customElements.flush = function() {
+        console.log('CustomElements flush');
+      };
+    }
+    if (!win.customElements.forcePolyfill) {
+      return;
+    }
+  }
+
   // name validation
   // https://html.spec.whatwg.org/multipage/scripting.html#valid-custom-element-name

@@ -61,7 +72,22 @@ var CustomElementDefinition;
   }

   function isElement(node) {
-    return node.nodeType === Node.ELEMENT_NODE
+    return node.nodeType === Node.ELEMENT_NODE;
+  }
+
+  function isHtmlImport(element) {
+    return element.tagName === 'LINK' &&
+        element.rel &&
+        element.rel.toLowerCase().split(' ').indexOf('import') !== -1;
+  }
+
+  function isConnected(element) {
+    var n = element;
+    do {
+      if (n.__attached || n === document) return true;
+      n = n.parentNode || n.nodeType === Node.DOCUMENT_FRAGMENT_NODE && n.host;
+    } while(n);
+    return false;
   }

   /**
@@ -93,8 +119,10 @@ var CustomElementDefinition;
     /** @private {HTMLElement} **/
     this._newInstance = null;

+    this._pendingHtmlImportUrls = new Set();
+
     this.polyfilled = true;
-    this.enableFlush = false;
+    this['enableFlush'] = false;

     this._observeRoot(document);
   }
@@ -153,10 +181,10 @@ var CustomElementDefinition;
             `constructor.prototype must be an object`);
       }

-      function getCallback(calllbackName) {
-        var callback = prototype[calllbackName];
+      function getCallback(callbackName) {
+        var callback = prototype[callbackName];
         if (callback !== undefined && typeof callback !== 'function') {
-          throw new Error(`${localName} '${calllbackName}' is not a Function`);
+          throw new Error(`${localName} '${callbackName}' is not a Function`);
         }
         return callback;
       }
@@ -177,7 +205,7 @@ var CustomElementDefinition;
       var observedAttributes = constructor['observedAttributes'] || [];

       // 15:
-      // @type {CustomElementDefinition}
+      /** @type {CustomElementDefinition} */
       var definition = {
         name: name,
         localName: localName,
@@ -247,7 +275,7 @@ var CustomElementDefinition;
      * `enableFlush` must be true for this to work. Only use during tests!
      */
     flush: function() {
-      if (this.enableFlush) {
+      if (this['enableFlush']) {
         console.warn("flush!!!");
         this._observers.forEach(function(observer) {
           this._handleMutations(observer.takeRecords());
@@ -264,9 +292,10 @@ var CustomElementDefinition;
      * @private
      */
     _observeRoot: function(root) {
+      console.assert(!root.__observer);
       root.__observer = new MutationObserver(this._handleMutations.bind(this));
       root.__observer.observe(root, {childList: true, subtree: true});
-      if (this.enableFlush) {
+      if (this['enableFlush']) {
         // this is memory leak, only use in tests
         this._observers.add(root.__observer);
       }
@@ -279,7 +308,7 @@ var CustomElementDefinition;
       if (root.__observer) {
         root.__observer.disconnect();
         root.__observer = null;
-        if (this.enableFlush) {
+        if (this['enableFlush']) {
           this._observers.delete(root.__observer);
         }
       }
@@ -302,9 +331,12 @@ var CustomElementDefinition;

     /**
      * @param {NodeList} nodeList
+     * @param {Set<Node>=} visitedNodes
      * @private
      */
-    _addNodes: function(nodeList) {
+    _addNodes: function(nodeList, visitedNodes) {
+      visitedNodes = visitedNodes || new Set();
+
       for (var i = 0; i < nodeList.length; i++) {
         var root = nodeList[i];

@@ -318,42 +350,80 @@ var CustomElementDefinition;
         var walker = createTreeWalker(root);
         do {
           var node = /** @type {HTMLElement} */ (walker.currentNode);
-          var definition = this._definitions.get(node.localName);
-          if (definition) {
-            if (!node.__upgraded) {
-              this._upgradeElement(node, definition, true);
-            }
-            if (node.__upgraded && !node.__attached) {
-              node.__attached = true;
-              if (definition && definition.connectedCallback) {
-                definition.connectedCallback.call(node);
-              }
-            }
-          }
-          if (node.shadowRoot) {
-            // TODO(justinfagnani): do we need to check that the shadowRoot
-            // is observed?
-            this._addNodes(node.shadowRoot.childNodes);
-          }
-          if (node.tagName === 'LINK') {
-            var onLoad = (function() {
-              var link = node;
-              return function() {
-                link.removeEventListener('load', onLoad);
-                this._observeRoot(link.import);
-                this._addNodes(link.import.childNodes);
-              }.bind(this);
-            }).bind(this)();
-            if (node.import) {
-              onLoad();
-            } else {
-              node.addEventListener('load', onLoad);
-            }
-          }
+          this._addElement(node, visitedNodes);
         } while (walker.nextNode())
       }
     },

+    _addElement(element, visitedNodes) {
+      if (visitedNodes.has(element)) return;
+      visitedNodes.add(element);
+
+      var definition = this._definitions.get(element.localName);
+      if (definition) {
+        if (!element.__upgraded) {
+          this._upgradeElement(element, definition, true);
+        }
+        // TODO(justinfagnani): check that the element is in the document
+        if (element.__upgraded && !element.__attached && isConnected(element)) {
+          element.__attached = true;
+          if (definition.connectedCallback) {
+            definition.connectedCallback.call(element);
+          }
+        }
+      }
+      if (element.shadowRoot) {
+        // TODO(justinfagnani): do we need to check that the shadowRoot
+        // is observed?
+        this._addNodes(element.shadowRoot.childNodes, visitedNodes);
+      }
+      if (isHtmlImport(element)) {
+        this._addImport(element, visitedNodes);
+      }
+    },
+
+    _addImport(link, visitedNodes) {
+      // During a tree walk to add or upgrade nodes, we may encounter multiple
+      // HTML imports that reference the same document, and may encounter
+      // imports in various states of loading.
+
+      // First, we only want to process the first import for a document in a
+      // walk, so we check visitedNodes for the document, not the link.
+      //
+      // Second, for documents that haven't loaded yet, we only want to add one
+      // listener, regardless of the number of links or walks, so we track
+      // pending loads in _pendingHtmlImportUrls.
+
+      // Check to see if the import is loaded
+      var _import = link.import;
+      if (_import) {
+        // The import is loaded, but only process the first link element
+        if (visitedNodes.has(_import)) return;
+        visitedNodes.add(_import);
+
+        // The import is loaded observe it
+        if (!_import.__observer) this._observeRoot(_import);
+
+        // walk the document
+        this._addNodes(_import.childNodes, visitedNodes);
+      } else {
+        // The import is not loaded, so wait for it
+        var importUrl = link.href;
+        if (this._pendingHtmlImportUrls.has(importUrl)) return;
+        this._pendingHtmlImportUrls.add(importUrl);
+
+        var _this = this;
+        var onLoad = function() {
+          link.removeEventListener('load', onLoad);
+          if (!link.import.__observer) _this._observeRoot(link.import);
+          // We don't pass visitedNodes because this is async and not part of
+          // the current tree walk.
+          _this._addNodes(link.import.childNodes);
+        };
+        link.addEventListener('load', onLoad);
+      }
+    },
+
     /**
      * @param {NodeList} nodeList
      * @private
@@ -405,7 +475,8 @@ var CustomElementDefinition;
       }

       var observedAttributes = definition.observedAttributes;
-      if (definition.attributeChangedCallback && observedAttributes.length > 0) {
+      var attributeChangedCallback = definition.attributeChangedCallback;
+      if (attributeChangedCallback && observedAttributes.length > 0) {
         this._attributeObserver.observe(element, {
           attributes: true,
           attributeOldValue: true,
@@ -418,7 +489,7 @@ var CustomElementDefinition;
           var name = observedAttributes[i];
           if (element.hasAttribute(name)) {
             var value = element.getAttribute(name);
-            element.attributeChangedCallback(name, null, value);
+            attributeChangedCallback.call(element, name, null, value);
           }
         }
       }
@@ -431,12 +502,15 @@ var CustomElementDefinition;
       for (var i = 0; i < mutations.length; i++) {
         var mutation = mutations[i];
         if (mutation.type === 'attributes') {
+          var target = mutation.target;
+          // We should be gaurenteed to have a definition because this mutation
+          // observer is only observing custom elements observedAttributes
+          var definition = this._definitions.get(target.localName);
           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);
+          definition.attributeChangedCallback.call(target, name, oldValue, newValue, namespace);
         }
       }
     },
@@ -449,23 +523,28 @@ var CustomElementDefinition;
   CustomElementsRegistry.prototype['whenDefined'] = CustomElementsRegistry.prototype.whenDefined;
   CustomElementsRegistry.prototype['flush'] = CustomElementsRegistry.prototype.flush;
   CustomElementsRegistry.prototype['polyfilled'] = CustomElementsRegistry.prototype.polyfilled;
-  CustomElementsRegistry.prototype['enableFlush'] = CustomElementsRegistry.prototype.enableFlush;
+  // TODO(justinfagnani): remove these in production code
+  CustomElementsRegistry.prototype['_observeRoot'] = CustomElementsRegistry.prototype._observeRoot;
+  CustomElementsRegistry.prototype['_addImport'] = CustomElementsRegistry.prototype._addImport;

   // patch window.HTMLElement

   var origHTMLElement = win.HTMLElement;
   win.HTMLElement = function HTMLElement() {
     var customElements = win['customElements'];
+
+    // If there's an being upgraded, return that
     if (customElements._newInstance) {
       var i = customElements._newInstance;
       customElements._newInstance = null;
       return i;
     }
     if (this.constructor) {
+      // Find the tagname of the constructor and create a new element with it
       var tagName = customElements._constructors.get(this.constructor);
       return doc._createElement(tagName, false);
     }
-    throw new Error('unknown constructor. Did you call customElements.define()?');
+    throw new Error('Unknown constructor. Did you call customElements.define()?');
   }
   win.HTMLElement.prototype = Object.create(origHTMLElement.prototype);
   Object.defineProperty(win.HTMLElement.prototype, 'constructor', {value: win.HTMLElement});
diff --git a/src/CustomElements/v1/README.md b/src/CustomElements/v1/README.md
index f76966f..57b7887 100644
--- a/src/CustomElements/v1/README.md
+++ b/src/CustomElements/v1/README.md
@@ -3,21 +3,18 @@
 ## Status

 The polyfill should be mostly feature complete now. It supports defining
-custom elements, the custom element reactions, upgrades, and integrates with
-native Shadow DOM v1, and native and polyfilled HTML Imports.
+custom elements, the custom element reactions, and upgrading existing elements. It integrates with native Shadow DOM v1, and native and polyfilled HTML Imports.

 The implementation could use more tests, especially around ordering of
 reactions. The source references old versions of the spec.

 ### To do

-  1. Implement CustomElementsRegistry#whenDefined
-  2. Implement Node#isConnected
-  3. Re-write spec references
-  4. Add reaction callback ordering tests
-  5. Reorganize tests to be closer to spec structure
-  6. Performance tests
-  7. Clarify integration with HTML Imports
+  1. Implement Node#isConnected
+  2. Implement built-in element extension (is=)
+  3. Add reaction callback ordering tests
+  4. Reorganize tests to be closer to spec structure
+  5. Performance tests

 ## Implementation approach and browser support

@@ -39,7 +36,7 @@ The HTMLElement constructor is also specified to look up the tag name associated

 `new.target` isn't even feature detectable, since it's a syntax error in ES5. Because of this, the polyfill can't check `new.target` first and fallback to `this.constructor`. This also means that ES5-style constructors can't conditionally make a "super" call to the HTMLElement constructor (with `Reflect.construct`) in non-ES6 environments to be compatible with native Custom Elements.

-To allow for elements that work in both ES5 and ES6 environments, we provide a shim that overrides the HTMLElement constructor and calls `Reflect.construct` with either the value of `new.target` or `this.constructor`. This shim can only be executed in ES6 environments.
+To allow for elements that work in both ES5 and ES6 environments, we provide a shim to be used in browsers that have native Custom Elements v1 support, that overrides the HTMLElement constructor and calls `Reflect.construct` with either the value of `new.target` or `this.constructor`. This shim can only be executed in ES6 environments that support `new.target`, and so should be conditionally loaded. The shim and the polyfill should not be loaded at the same time.

 ## Building

diff --git a/src/ShadowCSS/ShadowCSS.js b/src/ShadowCSS/ShadowCSS.js
index bee43ba..9f34f0d 100644
--- a/src/ShadowCSS/ShadowCSS.js
+++ b/src/ShadowCSS/ShadowCSS.js
@@ -586,7 +586,7 @@ var selectorRe = /([^{]*)({[\s\S]*?})/gim,
       /\/shadow\//g, // former ::shadow
       /\/shadow-deep\//g, // former /deep/
       /\^\^/g,     // former /shadow/
-      /\^/g        // former /shadow-deep/
+      /\^(?!=)/g   // former /shadow-deep/
     ];

 function stylesToCssText(styles, preserveComments) {
diff --git a/src/Template/Template.js b/src/Template/Template.js
index f393941..f43e72a 100644
--- a/src/Template/Template.js
+++ b/src/Template/Template.js
@@ -11,17 +11,38 @@
 // minimal template polyfill
 (function() {
   var needsTemplate = (typeof HTMLTemplateElement === 'undefined');
+  // NOTE: Patch document.importNode to work around IE11 bug that
+  // casues children of a document fragment imported while
+  // there is a mutation observer to not have a parentNode (!?!)
+  // This needs to happen *after* patching importNode to fix template cloning
+  if (/Trident/.test(navigator.userAgent)) {
+    (function() {
+      var importNode = document.importNode;
+      document.importNode = function() {
+        var n = importNode.apply(document, arguments);
+        // Copy all children to a new document fragment since
+        // this one may be broken
+        if (n.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
+          var f = document.createDocumentFragment();
+          f.appendChild(n);
+          return f;
+        } else {
+          return n;
+        }
+      };
+    })();
+  }

-  // returns true if nested templates can be cloned (they cannot be on 
+  // returns true if nested templates cannot be cloned (they cannot be on
   // some impl's like Safari 8)
   var needsCloning = (function() {
     if (!needsTemplate) {
-      var frag = document.createDocumentFragment();
       var t = document.createElement('template');
-      frag.appendChild(t);
-      t.content.appendChild(document.createElement('div'));
-      var clone = frag.cloneNode(true);
-      return (clone.firstChild.content.childNodes.length === 0);
+      var t2 = document.createElement('template');
+      t2.content.appendChild(document.createElement('div'));
+      t.content.appendChild(t2);
+      var clone = t.cloneNode(true);
+      return (clone.content.childNodes.length === 0 || clone.content.firstChild.content.childNodes.length === 0);
     }
   })();

@@ -42,7 +63,6 @@
     /**
       Provides a minimal shim for the <template> element.
     */
-    
     TemplateImpl.prototype = Object.create(HTMLElement.prototype);

     /**
@@ -59,6 +79,11 @@
       while (child = template.firstChild) {
         template.content.appendChild(child);
       }
+
+      template.cloneNode = function(deep) {
+        return TemplateImpl.cloneNode(this, deep);
+      };
+
       // add innerHTML to template, if possible
       // Note: this throws on Safari 7
       if (canDecorate) {
@@ -84,10 +109,6 @@
             configurable: true
           });

-          template.cloneNode = function(deep) {
-            return TemplateImpl.cloneNode(this, deep);
-          };
-
         } catch (err) {
           canDecorate = false;
         }
@@ -117,7 +138,7 @@
     document.createElement = function() {
       'use strict';
       var el = createElement.apply(document, arguments);
-      if (el.localName == 'template') {
+      if (el.localName === 'template') {
         TemplateImpl.decorate(el);
       }
       return el;
@@ -152,7 +173,7 @@
     var nativeCloneNode = Node.prototype.cloneNode;

     TemplateImpl.cloneNode = function(template, deep) {
-      var clone = nativeCloneNode.call(template);
+      var clone = nativeCloneNode.call(template, false);
       // NOTE: decorate doesn't auto-fix children because they are already
       // decorated so they need special clone fixup.
       if (this.decorate) {
@@ -169,7 +190,7 @@
       return clone;
     };

-    // Given a source and cloned subtree, find <template>'s in the cloned 
+    // Given a source and cloned subtree, find <template>'s in the cloned
     // subtree and replace them with cloned <template>'s from source.
     // We must do this because only the source templates have proper .content.
     TemplateImpl.fixClonedDom = function(clone, source) {
@@ -203,7 +224,7 @@

     // NOTE: we are cloning instead of importing <template>'s.
     // However, the ownerDocument of the cloned template will be correct!
-    // This is because the native import node creates the right document owned 
+    // This is because the native import node creates the right document owned
     // subtree and `fixClonedDom` inserts cloned templates into this subtree,
     // thus updating the owner doc.
     document.importNode = function(element, deep) {
@@ -223,7 +244,6 @@
         return TemplateImpl.cloneNode(this, deep);
       };
     }
-    
   }

   if (needsTemplate) {
diff --git a/src/WebComponents/dom.js b/src/WebComponents/dom.js
index 5c8c734..7959f42 100644
--- a/src/WebComponents/dom.js
+++ b/src/WebComponents/dom.js
@@ -14,7 +14,8 @@

   // polyfill performance.now

-  if (!window.performance) {
+  // Note: old Safari has performance, but not now().
+  if (!(window.performance && window.performance.now)) {
     var start = Date.now();
     // only at millisecond precision
     window.performance = {now: function(){ return Date.now() - start; }};
diff --git a/tests/CustomElements/js/customElements.js b/tests/CustomElements/js/customElements.js
index d61f227..91b58c7 100644
--- a/tests/CustomElements/js/customElements.js
+++ b/tests/CustomElements/js/customElements.js
@@ -183,9 +183,9 @@ suite('customElements', function() {
   });

   test('document.registerElement with type extension treats names as case insensitive', function() {
-    var proto = {prototype: Object.create(HTMLButtonElement.prototype), extends: 'button'};
+    var proto = {prototype: Object.create(HTMLButtonElement.prototype), extends: 'butTON'};
     proto.prototype.isXCase = true;
-    var XCase = document.registerElement('X-EXTEND-CASE', proto);
+    var XCase = document.registerElement('X-extend-CASE', proto);
     // createElement
     var x = document.createElement('button', 'X-EXTEND-CASE');
     assert.equal(x.isXCase, true);
diff --git a/tests/CustomElements/v1/html/imported-doc.html b/tests/CustomElements/v1/html/imported-doc.html
index ac8598e..ed889cc 100644
--- a/tests/CustomElements/v1/html/imported-doc.html
+++ b/tests/CustomElements/v1/html/imported-doc.html
@@ -1 +1,2 @@
+<link rel="import" href="sub-import.html">
 <x-foo></x-foo>
diff --git a/tests/CustomElements/v1/html/imports.html b/tests/CustomElements/v1/html/imports.html
index 0fb7ef9..987b293 100644
--- a/tests/CustomElements/v1/html/imports.html
+++ b/tests/CustomElements/v1/html/imports.html
@@ -30,29 +30,81 @@
       };

     </script>
-    <script src="../../../../dist/HTMLImports.js"></script>
-    <script src="../../../../src/CustomElements/v1/CustomElements.js"></script>
     <script src="../../../../../web-component-tester/browser.js"></script>
-    <link rel="import" href="imported-doc.html" id="import">
+    <script src="../../../../dist/HTMLImports.js"></script>
+    <script src="../../../../dist/CustomElementsV1.min.js"></script>
     <script>
       customElements.enableFlush = true;
       var elementsCreated = 0;
+      var connectedCount = 0;
+      var addedLinks = [];
+      var observedRoots = [];
+
+      // Patch customElements._addNode so we can spy on link nodes being
+      // processed.
+      var origAddImport = customElements._addImport;
+      // To patch a compiled method that's called internally in the class, we
+      // need to get the compiled, not exported, name:
+      var origAddImportName = origAddImport.name;
+      customElements[origAddImportName] = function(link, visitedNodes) {
+        addedLinks.push(link);
+        origAddImport.call(customElements, link, visitedNodes);
+      }
+
+      var origObserveRoot = customElements._observeRoot;
+      // To patch a compiled method that's called internally in the class, we
+      // need to get the compiled, not exported, name:
+      var origObserveRootName = origObserveRoot.name;
+      customElements[origObserveRootName] = function(root) {
+        observedRoots.push(root);
+        origObserveRoot.call(customElements, root);
+      }
+    </script>
+    <link rel="import" href="sub-import.html" id="sub-import">
+    <link rel="import" href="imported-doc.html" id="import">
+    <link rel="not-import" href="imported-doc.html" id="not-import">
+    <script>

+      // This element is used in the imports
       class XFoo extends HTMLElement {
         constructor() {
-          console.log("XFoo");
           super();
           elementsCreated++;
         }
+
+        connectedCallback() {
+          connectedCount++;
+        }
       }
       customElements.define('x-foo', XFoo);

       suite('HTML Imports Integration', function() {

-        test('CustomElements upgrade from HTML Imports', function() {
-          chai.assert.equal(2, elementsCreated, 'HTML hook allows main document and import document to upgrade');
+        test('CustomElements upgrade', function() {
+          chai.assert.equal(elementsCreated, 2);
         });
-        
+
+        test('CustomElements do not have connectedCallback called', function() {
+          // only the instance in the main document should be connected
+          chai.assert.equal(connectedCount, 1);
+        });
+
+        test('CustomElements only attempt to upgrade <link rel=import>', function() {
+          var nonImportLink = document.querySelector('#not-import');
+          var addedIds = addedLinks.map(function(n) {
+            return n.id;
+          });
+          chai.assert.notInclude(addedIds, 'not-import');
+        });
+
+        test('CustomElements only upgrades a <link rel=import> once', function() {
+          var observedSubImports = observedRoots.filter(function(n) {
+            return n.baseURI.endsWith('sub-import.html');
+          });
+          // check that we only observe an imported document once
+          chai.assert.equal(observedSubImports.length, 1);
+        });
+
       });
     </script>
   </head>
diff --git a/tests/CustomElements/v1/html/sub-import.html b/tests/CustomElements/v1/html/sub-import.html
new file mode 100644
index 0000000..7c89b54
--- /dev/null
+++ b/tests/CustomElements/v1/html/sub-import.html
@@ -0,0 +1 @@
+<div></div>
diff --git a/tests/CustomElements/v1/index.html b/tests/CustomElements/v1/index.html
index 5c26248..383bc16 100644
--- a/tests/CustomElements/v1/index.html
+++ b/tests/CustomElements/v1/index.html
@@ -11,8 +11,7 @@
 <title>CustomElements v1 Tests</title>
 <meta charset="utf-8">

-<!-- <script src="../../../dist/CustomElementsV1.min.js"></script> -->
-<script src="../../../src/CustomElements/v1/CustomElements.js"></script>
+<script src="../../../dist/CustomElementsV1.min.js"></script>
 <script src="../../../../web-component-tester/browser.js"></script>

 <script>
diff --git a/tests/CustomElements/v1/js/instanceof.js b/tests/CustomElements/v1/js/instanceof.js
index f3123d9..a8d4719 100644
--- a/tests/CustomElements/v1/js/instanceof.js
+++ b/tests/CustomElements/v1/js/instanceof.js
@@ -45,7 +45,7 @@ suite('Built-in Element instanceof', function() {
       'datalist',
       // 'dd',
       'del',
-      'details',
+      // 'details', // doesn't work on Safari 9
       // 'dfn',
       'dialog',
       'div',
@@ -124,7 +124,7 @@ suite('Built-in Element instanceof', function() {
       'tfoot',
       'th',
       'thead',
-      'time',
+      // 'time', // doesn't work on Safari 9
       'title',
       'tr',
       'track',
@@ -135,13 +135,6 @@ suite('Built-in Element instanceof', function() {
       // 'wbr',
     ];

-    console.log("HTMLElement", HTMLElement);
-
-    var d = document.createElement('abbr');
-    console.log(d);
-    console.log(d.__proto__);
-    console.log(d instanceof HTMLElement);
-
     for (var i = 0; i < elements.length; i++) {
       var tag = elements[i];
       var e = document.createElement(tag);
diff --git a/tests/CustomElements/v1/js/reactions.js b/tests/CustomElements/v1/js/reactions.js
index 3afc19b..81f4c08 100644
--- a/tests/CustomElements/v1/js/reactions.js
+++ b/tests/CustomElements/v1/js/reactions.js
@@ -239,29 +239,44 @@ suite('Custom Element Reactions', function() {
   });

   suite('connectedCallback', function() {
-
-    test('called when appended to main document', function() {
-      var inserted = 0;
-      class XBoo extends HTMLElement {
-        connectedCallback() {
-          inserted++;
-        }
+    var connectedCount;
+    class XConnected extends HTMLElement {
+      connectedCallback() {
+        connectedCount++;
       }
-      customElements.define('x-boo-at', XBoo);
+    }
+    customElements.define('x-connected', XConnected);

-      var xboo = new XBoo();
-      assert.equal(inserted, 0, 'inserted must be 0');
+    setup(function() {
+      connectedCount = 0;
+    });

-      work.appendChild(xboo);
+    test('is not called for disconnected custom elements', function() {
+      new XConnected();
+      assert.equal(connectedCount, 0);
+    });
+
+    test('is not called for deeply disconnected custom elements', function() {
+      var parent = new XConnected();
+      var child = new XConnected();
+      parent.appendChild(child);
       customElements.flush();
-      assert.equal(inserted, 1, 'inserted must be 1');
+      assert.equal(connectedCount, 0);
+    });

-      work.removeChild(xboo);
+    test('called when appended to main document', function() {
+      work.appendChild(new XConnected());
       customElements.flush();
-      assert(!xboo.parentNode);
-      work.appendChild(xboo);
+      assert.equal(connectedCount, 1);
+    });
+
+    test('called when re-appended to main document', function() {
+      var el = new XConnected();
+      work.appendChild(el);
+      work.removeChild(el);
+      work.appendChild(el);
       customElements.flush();
-      assert.equal(inserted, 2, 'inserted must be 2');
+      assert.equal(connectedCount, 2);
     });

     test('called in tree order', function() {
@@ -285,7 +300,7 @@ suite('Custom Element Reactions', function() {
           '</x-ordering>';

       customElements.flush();
-      assert.deepEqual(['a', 'b', 'c', 'd', 'e'], log);
+      assert.deepEqual(log, ['a', 'b', 'c', 'd', 'e']);
     });

   });
@@ -481,9 +496,10 @@ suite('Custom Element Reactions', function() {
       customElements.define('x-ad', XAD);
       var el = document.createElement('x-ad');
       work.appendChild(el);
+      customElements.flush();
       work.removeChild(el);
       customElements.flush();
-      assert.deepEqual(['connected', 'disconnected'], log);
+      assert.deepEqual(log, ['connected', 'disconnected']);
     });

     test('disconnected then re-connected in same task', function() {
@@ -504,7 +520,7 @@ suite('Custom Element Reactions', function() {
       work.removeChild(el);
       work.appendChild(el);
       customElements.flush();
-      assert.deepEqual(['disconnected', 'connected'], log);
+      assert.deepEqual(log, ['disconnected', 'connected']);
     });

   });
diff --git a/tests/Template/tests.html b/tests/Template/tests.html
index 1c9a2c2..3bf7440 100644
--- a/tests/Template/tests.html
+++ b/tests/Template/tests.html
@@ -37,6 +37,32 @@
           template = document.querySelector('template');
         });

+        var canInnerHTML = (function() {
+          var el = document.createElement('div');
+          try {
+            Object.defineProperty(el, 'innerHTML', {
+              get: function(){},
+              set: function(){}
+            });
+            return true;
+          } catch(e) {
+            return false;
+          }
+        })();
+
+        function setupTemplate(template, string) {
+          if (canInnerHTML) {
+            template.innerHTML = string;
+          } else {
+            var el = document.createElement('div');
+            el.innerHTML = string;
+            var nodes = Array.prototype.slice.call(el.childNodes, 0);
+            for (var i = 0; i < nodes.length; i++) {
+              template.content.appendChild(nodes[i]);
+            }
+          }
+        }
+
         test('No rendering', function() {
           var bcr = template.getBoundingClientRect();
           assert.equal(bcr.height, 0);
@@ -58,6 +84,9 @@
         });

         test('innerHTML', function() {
+          if (!canInnerHTML) {
+            this.skip();
+          }
           var imp = document.createElement('template');
           assert.equal(imp.innerHTML, '');
           var s = 'pre<div>Hi</div><div>Bye</div>post';
@@ -84,7 +113,7 @@
         test('clone', function() {
           var imp = document.createElement('template');
           var s = '<div>Hi</div>';
-          imp.innerHTML = s;
+          setupTemplate(imp, s);
           var clone = imp.cloneNode();
           assert.notEqual(clone, imp, 'element is not cloned');
           assert.isDefined(clone.content, 'cloned template content dne');
@@ -100,8 +129,8 @@
         test('clone nested', function() {
           var imp = document.createElement('template');
           var s = 'a<template id="a">b<template id="b">c<template id="c">d</template></template></template>';
-          imp.innerHTML = s;
-          var clone = imp.cloneNode();
+          setupTemplate(imp, s);
+          var clone = imp.cloneNode(false);
           assert.notEqual(clone, imp, 'element is not cloned');
           assert.isDefined(clone.content, 'cloned template content dne');
           assert.equal(clone.content.childNodes.length, 0,
@@ -129,7 +158,7 @@
           var imp = document.createElement('div');
           var t = document.createElement('template');
           var s = 'a<template id="a">b<template id="b">c<template id="c">d</template></template></template>';
-          t.innerHTML = s;
+          setupTemplate(t, s);
           imp.appendChild(t);
           var impClone = imp.cloneNode(true);
           var imp = imp.firstChild;
@@ -155,7 +184,7 @@
         test('importNode', function() {
           var imp = document.createElement('template');
           var s = '<div>Hi</div>';
-          imp.innerHTML = s;
+          setupTemplate(imp, s)
           var clone = document.importNode(imp, false);
           assert.notEqual(clone, imp, 'element is not cloned');
           assert.isDefined(clone.content, 'cloned template content dne');
@@ -171,7 +200,7 @@
         test('importNode: nested', function() {
           var imp = document.createElement('template');
           var s = 'a<template id="a">b<template id="b">c<template id="c">d</template></template></template>';
-          imp.innerHTML = s;
+          setupTemplate(imp, s);
           var clone = document.importNode(imp, false);
           assert.notEqual(clone, imp, 'element is not cloned');
           assert.isDefined(clone.content, 'cloned template content dne');
@@ -200,7 +229,7 @@
           var imp = document.createElement('div');
           var t = document.createElement('template');
           var s = 'a<template id="a">b<template id="b">c<template id="c">d</template></template></template>';
-          t.innerHTML = s;
+          setupTemplate(t, s);
           imp.appendChild(t);
           var impClone = document.importNode(imp, true);
           imp = imp.firstChild;
jpnelson commented 7 years ago

@justinfagnani can you confirm that there are no breaking changes between these two shas?

justinfagnani commented 7 years ago

Uh, no... what is this?

jpnelson commented 7 years ago

@justinfagnani sorry to pull you in, we're depending on a sha on the v1 branch of webcomponents.js 's CustomElement polyfill. It looks like bumping the version of that fixes some issues that we had with the polyfill. I was just wondering if there are any breaking changes in the diff, I'm struggling to tell.

Did you mean "no" as in you can't confirm? Or "no" as in there are no breaking changes?

justinfagnani commented 7 years ago

Well, I don't know what those SHAs point to, but there haven't been any spec changes that I've incorporated, so the public-facing API should be the same. The polyfill adds CustomElementsRegistry#flush(), but that should work the same as ever.

The polyfill isn't released yet though, so I can't commit to no changes. In particular I'm look at making the whole thing synchronous.

jpnelson commented 7 years ago

Ok, it's probably good for us to stay on top of the updates anyway. The later one is today, and I was comparing it with June 28th – looks like there have been quite a few changes in that time. I was just interested in whether there were any dramatic changes that you knew about.

Thanks!

lukebatchelor commented 7 years ago

LGTM