├── .bowerrc ├── src ├── .gitignore ├── base.js ├── traverse.js ├── boot.js ├── upgrade.js ├── observe.js └── register.js ├── .gitignore ├── README.md ├── test ├── js │ ├── karma-defer-tests.js │ ├── observe.js │ ├── upgrade.js │ ├── documentRegister.js │ └── customElements.js ├── runner.html └── html │ ├── upgrade-dcl.html │ ├── imports.html │ ├── attributes.html │ ├── shadowdom.html │ └── upgrade-order.html ├── banner.txt ├── package.json ├── bower.json ├── conf ├── mocha.conf.js └── karma.conf.js ├── CustomElements.js └── gruntfile.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "../" 3 | } 4 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | /custom-elements.min.js 2 | /custom-elements-source-map.js 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | npm-debug.log 3 | /custom-elements.min.js 4 | /custom-elements.min.source-map.js 5 | /docs 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Moved 2 | 3 | The Custom Elements polyfill has moved to https://github.com/webcomponents/webcomponentsjs 4 | 5 | For more information, see: http://webcomponents.org/polyfills/ 6 | 7 | -------------------------------------------------------------------------------- /test/js/karma-defer-tests.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 The Polymer Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | // defer start of tests until after WebComponentsReady event 8 | if (window.__karma__) { 9 | window.__karma__.loaded = function() { 10 | window.addEventListener('WebComponentsReady', function() { 11 | window.__karma__.start(); 12 | }); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /banner.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 4 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | * Code distributed by Google as part of the polymer project is also 8 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CustomElements", 3 | "description": "Polyfill for W3C CustomElements Specification", 4 | "version": "0.0.1", 5 | "devDependencies": { 6 | "chai": "*", 7 | "grunt": "*", 8 | "grunt-contrib-uglify": "*", 9 | "grunt-contrib-yuidoc": "~0.4.0", 10 | "grunt-karma": "*", 11 | "karma": "~0.12.0", 12 | "karma-mocha": "*", 13 | "karma-crbot-reporter": "*", 14 | "karma-firefox-launcher": "*", 15 | "karma-ie-launcher": "*", 16 | "karma-mocha": "*", 17 | "karma-safari-launcher": "*", 18 | "karma-script-launcher": "*", 19 | "mocha": "*" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CustomElements", 3 | "homepage": "https://github.com/Polymer/CustomElements", 4 | "authors": [ 5 | "The Polymer Authors" 6 | ], 7 | "description": "Custom Elements Polyfill", 8 | "main": "custom-elements.js", 9 | "keywords": [ 10 | "web", 11 | "components" 12 | ], 13 | "dependencies": { 14 | "MutationObservers": "Polymer/MutationObservers#master", 15 | "tools": "Polymer/tools#master" 16 | }, 17 | "license": "BSD", 18 | "private": true, 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "../", 24 | "test", 25 | "tests" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /conf/mocha.conf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @license 3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 4 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | * Code distributed by Google as part of the polymer project is also 8 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | mocha.setup({ 12 | ui:'tdd', 13 | htmlbase: '/base/CustomElements/test/' 14 | }); 15 | -------------------------------------------------------------------------------- /test/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | Test: Custom Elements 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/base.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 3 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 4 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 5 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 6 | * Code distributed by Google as part of the polymer project is also 7 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 8 | */ 9 | window.CustomElements = window.CustomElements || {flags:{}}; 10 | 11 | (function(scope) { 12 | 13 | // imports 14 | var flags = scope.flags; 15 | 16 | // world's simplest module initializer 17 | var modules = []; 18 | var addModule = function(module) { 19 | modules.push(module); 20 | }; 21 | 22 | var initializeModules = function() { 23 | modules.forEach(function(module) { 24 | module(scope); 25 | }); 26 | }; 27 | 28 | // exports 29 | scope.addModule = addModule; 30 | scope.initializeModules = initializeModules; 31 | scope.hasNative = Boolean(document.registerElement); 32 | 33 | // NOTE: For consistent timing, use native custom elements only when not 34 | // polyfilling other key related web components features. 35 | scope.useNative = !flags.register && scope.hasNative && 36 | !window.ShadowDOMPolyfill && (!window.HTMLImports || HTMLImports.useNative); 37 | 38 | })(CustomElements); -------------------------------------------------------------------------------- /conf/karma.conf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @license 3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 4 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | * Code distributed by Google as part of the polymer project is also 8 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | 11 | module.exports = function(karma) { 12 | var common = require('../../tools/test/karma-common.conf.js'); 13 | 14 | karma.set(common.mixin_common_opts(karma, { 15 | // base path, that will be used to resolve files and exclude 16 | basePath: '../../', 17 | 18 | // list of files / patterns to load in the browser 19 | files: [ 20 | 'tools/test/mocha-htmltest.js', 21 | 'CustomElements/conf/mocha.conf.js', 22 | 'CustomElements/../tools/test/chai/chai.js', 23 | 'CustomElements/custom-elements.js', 24 | 'CustomElements/test/js/*.js', 25 | {pattern: 'CustomElements/src/*', included: false}, 26 | {pattern: 'CustomElements/test/html/*.html', included: false}, 27 | {pattern: 'MutationObservers/*.js', included: false}, 28 | {pattern: 'WeakMap/*.js', included: false}, 29 | {pattern: 'tools/**/*.js', included: false} 30 | ] 31 | })); 32 | }; 33 | -------------------------------------------------------------------------------- /CustomElements.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 3 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 4 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 5 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 6 | * Code distributed by Google as part of the polymer project is also 7 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 8 | */ 9 | (function() { 10 | 11 | // Estblish polyfill scope. We do this here to store flags. Flags are not 12 | // supported in the build. 13 | window.CustomElements = window.CustomElements || {flags:{}}; 14 | 15 | // Flags. Convert url arguments to flags 16 | var flags = {}; 17 | if (!flags.noOpts) { 18 | location.search.slice(1).split('&').forEach(function(o) { 19 | o = o.split('='); 20 | o[0] && (flags[o[0]] = o[1] || true); 21 | }); 22 | } 23 | 24 | 25 | // Load. 26 | var file = 'CustomElements.js'; 27 | 28 | var modules = [ 29 | '../MutationObservers/mutation-observers.js', 30 | 'src/base.js', 31 | 'src/traverse.js', 32 | 'src/observe.js', 33 | 'src/upgrade.js', 34 | 'src/register.js', 35 | 'src/boot.js' 36 | ]; 37 | 38 | var src = 39 | document.querySelector('script[src*="' + file + '"]').getAttribute('src'); 40 | var basePath = src.slice(0, src.indexOf(file)); 41 | 42 | modules.forEach(function(f) { 43 | document.write(''); 44 | }); 45 | 46 | // exports 47 | CustomElements.flags = flags; 48 | 49 | })(); -------------------------------------------------------------------------------- /test/html/upgrade-dcl.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | Custom Elements: upgrade order 14 | 15 | 16 | 17 | 18 | 19 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 The Polymer Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | module.exports = function(grunt) { 7 | var readManifest = require('../tools/loader/readManifest.js'); 8 | 9 | CustomElements = readManifest('build.json'); 10 | grunt.initConfig({ 11 | karma: { 12 | options: { 13 | configFile: 'conf/karma.conf.js', 14 | keepalive: true 15 | }, 16 | buildbot: { 17 | reporters: ['crbot'], 18 | logLevel: 'OFF' 19 | }, 20 | CustomElements: { 21 | } 22 | }, 23 | uglify: { 24 | CustomElements: { 25 | options: { 26 | // sourceMap: 'custom-elements.min.source-map.js' 27 | banner: grunt.file.read('banner.txt') 28 | }, 29 | files: { 30 | 'custom-elements.min.js': CustomElements 31 | } 32 | } 33 | }, 34 | yuidoc: { 35 | compile: { 36 | name: '<%= pkg.name %>', 37 | description: '<%= pkg.description %>', 38 | version: '<%= pkg.version %>', 39 | url: '<%= pkg.homepage %>', 40 | options: { 41 | exclude: 'third_party', 42 | paths: '.', 43 | outdir: 'docs', 44 | linkNatives: 'true', 45 | tabtospace: 2, 46 | themedir: '../tools/doc/themes/polymerase' 47 | } 48 | } 49 | }, 50 | pkg: grunt.file.readJSON('package.json') 51 | }); 52 | 53 | grunt.loadTasks('../tools/tasks'); 54 | // plugins 55 | grunt.loadNpmTasks('grunt-contrib-uglify'); 56 | grunt.loadNpmTasks('grunt-contrib-yuidoc'); 57 | grunt.loadNpmTasks('grunt-karma'); 58 | 59 | // tasks 60 | grunt.registerTask('default', ['uglify']); 61 | grunt.registerTask('minify', ['uglify']); 62 | grunt.registerTask('docs', ['yuidoc']); 63 | grunt.registerTask('test', ['override-chrome-launcher', 'karma:CustomElements']); 64 | grunt.registerTask('test-buildbot', ['override-chrome-launcher', 'karma:buildbot']); 65 | }; 66 | 67 | -------------------------------------------------------------------------------- /test/html/imports.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | Custom Elements: imports integration 14 | 15 | 16 | 40 | 41 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /test/html/attributes.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | Custom Elements: attributes 14 | 15 | 16 | 17 | 18 | 19 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /test/html/shadowdom.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | Custom Elements: shadowdom integration 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /test/html/upgrade-order.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | Custom Elements: upgrade order 14 | 15 | 16 | 17 | 18 | 19 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /test/js/observe.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 The Polymer Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | suite('observe', function() { 8 | var work; 9 | var assert = chai.assert; 10 | 11 | setup(function() { 12 | work = document.createElement('div'); 13 | document.body.appendChild(work); 14 | }); 15 | 16 | teardown(function() { 17 | document.body.removeChild(work); 18 | }); 19 | 20 | function registerTestComponent(inName, inValue) { 21 | var proto = Object.create(HTMLElement.prototype); 22 | proto.value = inValue || 'value'; 23 | document.registerElement(inName, { 24 | prototype: proto 25 | }); 26 | } 27 | 28 | function testElements(node, selector, value) { 29 | Array.prototype.forEach.call(node.querySelectorAll(selector), function(n) { 30 | assert.equal(n.value, value); 31 | }); 32 | } 33 | 34 | test('custom element automatically upgrades', function(done) { 35 | work.innerHTML = ''; 36 | var x = work.firstChild; 37 | assert.isUndefined(x.value); 38 | registerTestComponent('x-auto', 'auto'); 39 | assert.equal(x.value, 'auto'); 40 | done(); 41 | }); 42 | 43 | test('custom element automatically upgrades in subtree', function(done) { 44 | work.innerHTML = '
'; 45 | var target = work.firstChild; 46 | target.innerHTML = ''; 47 | var x = target.firstChild; 48 | assert.isUndefined(x.value); 49 | registerTestComponent('x-auto-sub', 'auto-sub'); 50 | assert.equal(x.value, 'auto-sub'); 51 | done(); 52 | }); 53 | 54 | test('custom elements automatically upgrade', function(done) { 55 | registerTestComponent('x-auto1', 'auto1'); 56 | registerTestComponent('x-auto2', 'auto2'); 57 | work.innerHTML = '
' + 58 | '
' + 59 | '
'; 60 | CustomElements.takeRecords(); 61 | testElements(work, 'x-auto1', 'auto1'); 62 | testElements(work, 'x-auto2', 'auto2'); 63 | done(); 64 | }); 65 | 66 | test('custom elements automatically upgrade in subtree', function(done) { 67 | registerTestComponent('x-auto-sub1', 'auto-sub1'); 68 | registerTestComponent('x-auto-sub2', 'auto-sub2'); 69 | work.innerHTML = '
'; 70 | var target = work.firstChild; 71 | target.innerHTML = '
' + 72 | '
' + 73 | '
'; 74 | CustomElements.takeRecords(); 75 | testElements(target, 'x-auto-sub1', 'auto-sub1'); 76 | testElements(target, 'x-auto-sub2', 'auto-sub2'); 77 | done(); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /src/traverse.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 3 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 4 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 5 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 6 | * Code distributed by Google as part of the polymer project is also 7 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 8 | */ 9 | 10 | // helper methods for traversing through element trees 11 | CustomElements.addModule(function(scope){ 12 | 13 | // imports 14 | var IMPORT_LINK_TYPE = window.HTMLImports ? HTMLImports.IMPORT_LINK_TYPE : 'none'; 15 | 16 | // walk the subtree rooted at node, including descent into shadow-roots, 17 | // applying 'cb' to each element 18 | function forSubtree(node, cb) { 19 | //flags.dom && node.childNodes && node.childNodes.length && console.group('subTree: ', node); 20 | findAllElements(node, function(e) { 21 | if (cb(e)) { 22 | return true; 23 | } 24 | forRoots(e, cb); 25 | }); 26 | forRoots(node, cb); 27 | //flags.dom && node.childNodes && node.childNodes.length && console.groupEnd(); 28 | } 29 | 30 | 31 | // walk the subtree rooted at node, applying 'find(element, data)' function 32 | // to each element 33 | // if 'find' returns true for 'element', do not search element's subtree 34 | function findAllElements(node, find, data) { 35 | var e = node.firstElementChild; 36 | if (!e) { 37 | e = node.firstChild; 38 | while (e && e.nodeType !== Node.ELEMENT_NODE) { 39 | e = e.nextSibling; 40 | } 41 | } 42 | while (e) { 43 | if (find(e, data) !== true) { 44 | findAllElements(e, find, data); 45 | } 46 | e = e.nextElementSibling; 47 | } 48 | return null; 49 | } 50 | 51 | // walk all shadowRoots on a given node. 52 | function forRoots(node, cb) { 53 | var root = node.shadowRoot; 54 | while(root) { 55 | forSubtree(root, cb); 56 | root = root.olderShadowRoot; 57 | } 58 | } 59 | 60 | /* 61 | Note that the import tree can consume itself and therefore special care 62 | must be taken to avoid recursion. 63 | */ 64 | var processingDocuments; 65 | function forDocumentTree(doc, cb) { 66 | processingDocuments = []; 67 | _forDocumentTree(doc, cb); 68 | processingDocuments = null; 69 | } 70 | 71 | 72 | function _forDocumentTree(doc, cb) { 73 | doc = wrap(doc); 74 | if (processingDocuments.indexOf(doc) >= 0) { 75 | return; 76 | } 77 | processingDocuments.push(doc); 78 | var imports = doc.querySelectorAll('link[rel=' + IMPORT_LINK_TYPE + ']'); 79 | for (var i=0, l=imports.length, n; (i' + 108 | '
'; 109 | CustomElements.upgradeAll(work); 110 | var b$ = work.querySelectorAll('[is=y-button]'); 111 | Array.prototype.forEach.call(b$, function(b, i) { 112 | assert.equal(b.test, 'ybutton'); 113 | assert.equal(b.textContent, i); 114 | }); 115 | }); 116 | 117 | }); -------------------------------------------------------------------------------- /src/boot.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 3 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 4 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 5 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 6 | * Code distributed by Google as part of the polymer project is also 7 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 8 | */ 9 | (function(scope){ 10 | 11 | // imports 12 | var useNative = scope.useNative; 13 | var initializeModules = scope.initializeModules; 14 | 15 | // If native, setup stub api and bail. 16 | // NOTE: we fire `WebComponentsReady` under native for api compatibility 17 | if (useNative) { 18 | // stub 19 | var nop = function() {}; 20 | 21 | // exports 22 | scope.watchShadow = nop; 23 | scope.upgradeAll = nop; 24 | scope.upgradeDocumentTree = nop; 25 | scope.takeRecords = nop; 26 | scope.instanceof = function(obj, base) { 27 | return obj instanceof base; 28 | }; 29 | 30 | } else { 31 | // Initialize polyfill modules. Note, polyfill modules are loaded but not 32 | // executed; this is a convenient way to control which modules run when 33 | // the polyfill is required and allows the polyfill to load even when it's 34 | // not needed. 35 | initializeModules(); 36 | } 37 | 38 | // imports 39 | var upgradeDocumentTree = scope.upgradeDocumentTree; 40 | 41 | // ShadowDOM polyfill wraps elements but some elements like `document` 42 | // cannot be wrapped so we help the polyfill by wrapping some elements. 43 | if (!window.wrap) { 44 | if (window.ShadowDOMPolyfill) { 45 | window.wrap = ShadowDOMPolyfill.wrapIfNeeded; 46 | window.unwrap = ShadowDOMPolyfill.unwrapIfNeeded; 47 | } else { 48 | window.wrap = window.unwrap = function(node) { 49 | return node; 50 | }; 51 | } 52 | } 53 | 54 | // bootstrap parsing 55 | function bootstrap() { 56 | // parse document 57 | upgradeDocumentTree(wrap(document)); 58 | // install upgrade hook if HTMLImports are available 59 | if (window.HTMLImports) { 60 | HTMLImports.__importsParsingHook = function(elt) { 61 | upgradeDocumentTree(wrap(elt.import)); 62 | //CustomElements.parser.parse(elt.import); 63 | }; 64 | } 65 | // set internal 'ready' flag, now document.registerElement will trigger 66 | // synchronous upgrades 67 | CustomElements.ready = true; 68 | // async to ensure *native* custom elements upgrade prior to this 69 | // DOMContentLoaded can fire before elements upgrade (e.g. when there's 70 | // an external script) 71 | setTimeout(function() { 72 | // capture blunt profiling data 73 | CustomElements.readyTime = Date.now(); 74 | if (window.HTMLImports) { 75 | CustomElements.elapsed = CustomElements.readyTime - HTMLImports.readyTime; 76 | } 77 | // notify the system that we are bootstrapped 78 | document.dispatchEvent( 79 | new CustomEvent('WebComponentsReady', {bubbles: true}) 80 | ); 81 | }); 82 | } 83 | 84 | // CustomEvent shim for IE 85 | if (typeof window.CustomEvent !== 'function') { 86 | window.CustomEvent = function(inType, params) { 87 | params = params || {}; 88 | var e = document.createEvent('CustomEvent'); 89 | e.initCustomEvent(inType, Boolean(params.bubbles), Boolean(params.cancelable), params.detail); 90 | return e; 91 | }; 92 | window.CustomEvent.prototype = window.Event.prototype; 93 | } 94 | 95 | // When loading at readyState complete time (or via flag), boot custom elements 96 | // immediately. 97 | // If relevant, HTMLImports must already be loaded. 98 | if (document.readyState === 'complete' || scope.flags.eager) { 99 | bootstrap(); 100 | // When loading at readyState interactive time, bootstrap only if HTMLImports 101 | // are not pending. Also avoid IE as the semantics of this state are unreliable. 102 | } else if (document.readyState === 'interactive' && !window.attachEvent && 103 | (!window.HTMLImports || window.HTMLImports.ready)) { 104 | bootstrap(); 105 | // When loading at other readyStates, wait for the appropriate DOM event to 106 | // bootstrap. 107 | } else { 108 | var loadEvent = window.HTMLImports && !HTMLImports.ready ? 109 | 'HTMLImportsLoaded' : 'DOMContentLoaded'; 110 | window.addEventListener(loadEvent, bootstrap); 111 | } 112 | 113 | })(window.CustomElements); 114 | -------------------------------------------------------------------------------- /src/upgrade.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 3 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 4 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 5 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 6 | * Code distributed by Google as part of the polymer project is also 7 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 8 | */ 9 | 10 | /** 11 | * Implements custom element upgrading 12 | * @module upgrade 13 | */ 14 | 15 | CustomElements.addModule(function(scope) { 16 | 17 | // imports 18 | var flags = scope.flags; 19 | 20 | /** 21 | * Upgrade an element to a custom element. Upgrading an element 22 | * causes the custom prototype to be applied, an `is` attribute 23 | * to be attached (as needed), and invocation of the `readyCallback`. 24 | * If the element is in the main document, the `attachedkCallback` method 25 | * will be invoked. 26 | * `upgrade` does nothing if the element is already upgraded, or 27 | * if it matches no registered custom tag name. 28 | * 29 | * @method ugprade 30 | * @param {Element} element The element to upgrade. 31 | * @return {Element} The upgraded element. 32 | */ 33 | // Upgrade a node if it can be upgraded and is not already. 34 | function upgrade(node) { 35 | if (!node.__upgraded__ && (node.nodeType === Node.ELEMENT_NODE)) { 36 | var is = node.getAttribute('is'); 37 | var definition = scope.getRegisteredDefinition(is || node.localName); 38 | if (definition) { 39 | if (is && definition.tag == node.localName) { 40 | return upgradeWithDefinition(node, definition); 41 | } else if (!is && !definition.extends) { 42 | return upgradeWithDefinition(node, definition); 43 | } 44 | } 45 | } 46 | } 47 | 48 | function upgradeWithDefinition(element, definition) { 49 | flags.upgrade && console.group('upgrade:', element.localName); 50 | // some definitions specify an 'is' attribute 51 | if (definition.is) { 52 | element.setAttribute('is', definition.is); 53 | } 54 | // make 'element' implement definition.prototype 55 | implementPrototype(element, definition); 56 | // flag as upgraded 57 | element.__upgraded__ = true; 58 | // lifecycle management 59 | created(element); 60 | // attachedCallback fires in tree order, call before recursing 61 | scope.attachedNode(element); 62 | // there should never be a shadow root on element at this point 63 | scope.upgradeSubtree(element); 64 | flags.upgrade && console.groupEnd(); 65 | // OUTPUT 66 | return element; 67 | } 68 | 69 | // Set __proto__ on supported platforms and use a mixin strategy when 70 | // this is not supported; e.g. on IE10. 71 | function implementPrototype(element, definition) { 72 | // prototype swizzling is best 73 | if (Object.__proto__) { 74 | element.__proto__ = definition.prototype; 75 | } else { 76 | // where above we can re-acquire inPrototype via 77 | // getPrototypeOf(Element), we cannot do so when 78 | // we use mixin, so we install a magic reference 79 | customMixin(element, definition.prototype, definition.native); 80 | element.__proto__ = definition.prototype; 81 | } 82 | } 83 | 84 | function customMixin(inTarget, inSrc, inNative) { 85 | // TODO(sjmiles): 'used' allows us to only copy the 'youngest' version of 86 | // any property. This set should be precalculated. We also need to 87 | // consider this for supporting 'super'. 88 | var used = {}; 89 | // start with inSrc 90 | var p = inSrc; 91 | // The default is HTMLElement.prototype, so we add a test to avoid mixing in 92 | // native prototypes 93 | while (p !== inNative && p !== HTMLElement.prototype) { 94 | var keys = Object.getOwnPropertyNames(p); 95 | for (var i=0, k; k=keys[i]; i++) { 96 | if (!used[k]) { 97 | Object.defineProperty(inTarget, k, 98 | Object.getOwnPropertyDescriptor(p, k)); 99 | used[k] = 1; 100 | } 101 | } 102 | p = Object.getPrototypeOf(p); 103 | } 104 | } 105 | 106 | function created(element) { 107 | // invoke createdCallback 108 | if (element.createdCallback) { 109 | element.createdCallback(); 110 | } 111 | } 112 | 113 | scope.upgrade = upgrade; 114 | scope.upgradeWithDefinition = upgradeWithDefinition; 115 | scope.implementPrototype = implementPrototype; 116 | 117 | }); 118 | -------------------------------------------------------------------------------- /test/js/documentRegister.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 The Polymer Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | 8 | // Adapted from: 9 | // https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/LayoutTests/fast/dom/custom/document-register-type-extensions.html 10 | var testForm = document.createElement('form'); 11 | 12 | function isFormControl(element) 13 | { 14 | testForm.appendChild(element); 15 | return element.form == testForm; 16 | } 17 | 18 | 19 | /* 20 | * Work around IE's insertion of XML Namespace elements into .outerHTML of HTMLUnknownElements 21 | * 22 | * Clone the input node, insert it into a div, and then read back the outerHTML, which is now stripped of the XML * 23 | * Namespace element 24 | */ 25 | var isIE = navigator.userAgent.indexOf('Trident') > -1; 26 | function assertOuterHTML(element, expected) { 27 | var outerHTML = element.outerHTML; 28 | if (isIE) { 29 | var div = document.createElement('div'); 30 | div.appendChild(element.cloneNode(true)); 31 | outerHTML = div.firstChild.outerHTML; 32 | } 33 | chai.assert.equal(outerHTML, expected); 34 | } 35 | 36 | var hasProto = ({}.__proto__); 37 | function assertInstanceOf(element, constructor) { 38 | if (hasProto) { 39 | chai.assert.instanceOf(element, constructor); 40 | } 41 | } 42 | 43 | function assertNotInstanceOf(element, constructor) { 44 | if (hasProto) { 45 | chai.assert.notInstanceOf(element, constructor); 46 | } 47 | } 48 | 49 | suite('register-type-extensions', function() { 50 | var assert = chai.assert; 51 | 52 | var fooConstructor = document.registerElement('x-foo-x', { 53 | prototype: Object.create(HTMLElement.prototype) }); 54 | var fooOuterHTML = ''; 55 | var barConstructor = document.registerElement('x-bar-x', { 56 | prototype: Object.create(HTMLInputElement.prototype), 57 | extends:'input'}); 58 | var barOuterHTML = ''; 59 | var bazConstructor = document.registerElement('x-baz', { 60 | prototype: Object.create(fooConstructor.prototype) }); 61 | var quxConstructor = document.registerElement('x-qux', { 62 | prototype: Object.create(barConstructor.prototype), 63 | extends:'input'}); 64 | 65 | test('cannot register twice', function() { 66 | assert.throws(function() { 67 | document.registerElement('x-foo-x', { 68 | prototype: Object.create(HTMLDivElement.prototype) }); 69 | }); 70 | }); 71 | 72 | suite('generated constructors', function() { 73 | test('custom tag', function() { 74 | var fooNewed = new fooConstructor(); 75 | assertOuterHTML(fooNewed, fooOuterHTML); 76 | assertInstanceOf(fooNewed, fooConstructor); 77 | assertInstanceOf(fooNewed, HTMLElement); 78 | // This is part of the Blink tests, but not supported in Firefox with 79 | // polyfill. Similar assertions are also commented out below. 80 | // assertNotInstanceOf(fooNewed, HTMLUnknownElement); 81 | 82 | test('custom tag constructor', function() { 83 | assert.equal('a', 'b'); 84 | }); 85 | }); 86 | 87 | test('type extension', function() { 88 | var barNewed = new barConstructor(); 89 | assertOuterHTML(barNewed, barOuterHTML); 90 | assertInstanceOf(barNewed, barConstructor); 91 | assertInstanceOf(barNewed, HTMLInputElement); 92 | assert.ok(isFormControl(barNewed)); 93 | }); 94 | 95 | test('custom tag deriving from custom tag', function() { 96 | var bazNewed = new bazConstructor(); 97 | var bazOuterHTML = ''; 98 | assertOuterHTML(bazNewed, bazOuterHTML); 99 | assertInstanceOf(bazNewed, bazConstructor); 100 | assertInstanceOf(bazNewed, HTMLElement); 101 | // assertNotInstanceOf(bazNewed, HTMLUnknownElement); 102 | }); 103 | 104 | test('type extension deriving from custom tag', function() { 105 | var quxNewed = new quxConstructor(); 106 | var quxOuterHTML = ''; 107 | assertInstanceOf(quxNewed, quxConstructor); 108 | assertInstanceOf(quxNewed, barConstructor); 109 | assertInstanceOf(quxNewed, HTMLInputElement); 110 | assertOuterHTML(quxNewed, quxOuterHTML); 111 | assert.ok(isFormControl(quxNewed)); 112 | }); 113 | }); 114 | 115 | suite('single-parameter createElement', function() { 116 | test('custom tag', function() { 117 | var fooCreated = document.createElement('x-foo-x'); 118 | assertOuterHTML(fooCreated, fooOuterHTML); 119 | assertInstanceOf(fooCreated, fooConstructor); 120 | }); 121 | 122 | test('type extension', function() { 123 | var barCreated = document.createElement('x-bar-x'); 124 | assertOuterHTML(barCreated, ''); 125 | assertNotInstanceOf(barCreated, barConstructor); 126 | // assertNotInstanceOf(barCreated, HTMLUnknownElement); 127 | assertInstanceOf(barCreated, HTMLElement); 128 | }); 129 | 130 | test('custom tag deriving from custom tag', function() { 131 | bazCreated = document.createElement('x-baz'); 132 | assertOuterHTML(bazCreated, ''); 133 | assertInstanceOf(bazCreated, bazConstructor); 134 | // assertNotInstanceOf(bazCreated, HTMLUnknownElement); 135 | }); 136 | 137 | test('type extension deriving from custom tag', function() { 138 | quxCreated = document.createElement('x-qux'); 139 | assertOuterHTML(quxCreated, ''); 140 | assertNotInstanceOf(quxCreated, quxConstructor); 141 | // assertNotInstanceOf(quxCreated, HTMLUnknownElement); 142 | assertInstanceOf(quxCreated, HTMLElement); 143 | }); 144 | }); 145 | 146 | suite('createElement with type extensions', function() { 147 | test('extension is custom tag', function() { 148 | var divFooCreated = document.createElement('div', 'x-foo-x'); 149 | assertOuterHTML(divFooCreated, '
'); 150 | assertNotInstanceOf(divFooCreated, fooConstructor); 151 | assertInstanceOf(divFooCreated, HTMLDivElement); 152 | }); 153 | 154 | test('valid extension', function() { 155 | var inputBarCreated = document.createElement('input', 'x-bar-x'); 156 | assertOuterHTML(inputBarCreated, barOuterHTML); 157 | assertInstanceOf(inputBarCreated, barConstructor); 158 | assertNotInstanceOf(inputBarCreated, HTMLUnknownElement); 159 | assert.ok(isFormControl(inputBarCreated)); 160 | }); 161 | 162 | test('type extension of incorrect tag', function() { 163 | var divBarCreated = document.createElement('div', 'x-bar-x'); 164 | assertOuterHTML(divBarCreated, '
'); 165 | assertNotInstanceOf(divBarCreated, barConstructor); 166 | assertInstanceOf(divBarCreated, HTMLDivElement); 167 | }); 168 | 169 | test('incorrect extension of custom tag', function() { 170 | var fooBarCreated = document.createElement('x-foo-x', 'x-bar-x'); 171 | assertOuterHTML(fooBarCreated, ''); 172 | assertInstanceOf(fooBarCreated, fooConstructor); 173 | }); 174 | 175 | test('incorrect extension of type extension', function() { 176 | var barFooCreated = document.createElement('x-bar-x', 'x-foo-x'); 177 | assertOuterHTML(barFooCreated, ''); 178 | // assertNotInstanceOf(barFooCreated, HTMLUnknownElement); 179 | assertInstanceOf(barFooCreated, HTMLElement); 180 | }); 181 | 182 | test('null type extension', function() { 183 | var fooCreatedNull = document.createElement('x-foo-x', null); 184 | assertOuterHTML(fooCreatedNull, fooOuterHTML); 185 | assertInstanceOf(fooCreatedNull, fooConstructor); 186 | }); 187 | 188 | test('empty type extension', function() { 189 | fooCreatedEmpty = document.createElement('x-foo-x', ''); 190 | assertOuterHTML(fooCreatedEmpty, fooOuterHTML); 191 | assertInstanceOf(fooCreatedEmpty, fooConstructor); 192 | }); 193 | 194 | test('invalid tag name', function() { 195 | assert.throws(function() { 196 | document.createElement('@invalid', 'x-bar-x'); 197 | }); 198 | }); 199 | }); 200 | 201 | suite('parser', function() { 202 | function createElementFromHTML(html) { 203 | var container = document.createElement('div'); 204 | container.innerHTML = html; 205 | if (window.CustomElements) { 206 | window.CustomElements.upgradeAll(container); 207 | } 208 | return container.firstChild; 209 | } 210 | 211 | test('custom tag', function() { 212 | var fooParsed = createElementFromHTML(''); 213 | assertInstanceOf(fooParsed, fooConstructor); 214 | }); 215 | 216 | test('type extension', function() { 217 | var barParsed = createElementFromHTML(''); 218 | assertInstanceOf(barParsed, barConstructor); 219 | assert.ok(isFormControl(barParsed)); 220 | }); 221 | 222 | test('custom tag as type extension', function() { 223 | var divFooParsed = createElementFromHTML('
'); 224 | assertNotInstanceOf(divFooParsed, fooConstructor); 225 | assertInstanceOf(divFooParsed, HTMLDivElement); 226 | }); 227 | 228 | // Should we upgrade invalid tags to HTMLElement? 229 | /*test('type extension as custom tag', function() { 230 | var namedBarParsed = createElementFromHTML('') 231 | assertNotInstanceOf(namedBarParsed, barConstructor); 232 | assertNotInstanceOf(namedBarParsed, HTMLUnknownElement); 233 | assertInstanceOf(namedBarParsed, HTMLElement); 234 | });*/ 235 | 236 | test('type extension of incorrect tag', function() { 237 | var divBarParsed = createElementFromHTML('
'); 238 | assertNotInstanceOf(divBarParsed, barConstructor); 239 | assertInstanceOf(divBarParsed, HTMLDivElement); 240 | }); 241 | }); 242 | }); 243 | -------------------------------------------------------------------------------- /src/observe.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 3 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 4 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 5 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 6 | * Code distributed by Google as part of the polymer project is also 7 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 8 | */ 9 | 10 | /** 11 | * Implements custom element observation and attached/detached callbacks 12 | * @module observe 13 | */ 14 | 15 | CustomElements.addModule(function(scope){ 16 | 17 | // imports 18 | var flags = scope.flags; 19 | var forSubtree = scope.forSubtree; 20 | var forDocumentTree = scope.forDocumentTree; 21 | 22 | /* 23 | Manage nodes attached to document trees 24 | */ 25 | 26 | // manage lifecycle on added node and it's subtree; upgrade the node and 27 | // entire subtree if necessary and process attached for the node and entire 28 | // subtree 29 | function addedNode(node) { 30 | return added(node) || addedSubtree(node); 31 | } 32 | 33 | // manage lifecycle on added node; upgrade if necessary and process attached 34 | function added(node) { 35 | if (scope.upgrade(node)) { 36 | return true; 37 | } 38 | attached(node); 39 | } 40 | 41 | // manage lifecycle on added node's subtree only; allows the entire subtree 42 | // to upgrade if necessary and process attached 43 | function addedSubtree(node) { 44 | forSubtree(node, function(e) { 45 | if (added(e)) { 46 | return true; 47 | } 48 | }); 49 | } 50 | 51 | function attachedNode(node) { 52 | attached(node); 53 | // only check subtree if node is actually in document 54 | if (inDocument(node)) { 55 | forSubtree(node, function(e) { 56 | attached(e); 57 | }); 58 | } 59 | } 60 | 61 | // On platforms without MutationObserver, mutations may not be 62 | // reliable and therefore attached/detached are not reliable. 63 | // To make these callbacks less likely to fail, we defer all inserts and removes 64 | // to give a chance for elements to be attached into dom. 65 | // This ensures attachedCallback fires for elements that are created and 66 | // immediately added to dom. 67 | var hasPolyfillMutations = (!window.MutationObserver || 68 | (window.MutationObserver === window.JsMutationObserver)); 69 | scope.hasPolyfillMutations = hasPolyfillMutations; 70 | 71 | var isPendingMutations = false; 72 | var pendingMutations = []; 73 | function deferMutation(fn) { 74 | pendingMutations.push(fn); 75 | if (!isPendingMutations) { 76 | isPendingMutations = true; 77 | var async = (window.Platform && window.Platform.endOfMicrotask) || 78 | setTimeout; 79 | async(takeMutations); 80 | } 81 | } 82 | 83 | function takeMutations() { 84 | isPendingMutations = false; 85 | var $p = pendingMutations; 86 | for (var i=0, l=$p.length, p; (i= 0) { 291 | implementPrototype(element, HTMLElement); 292 | } 293 | return element; 294 | } 295 | 296 | function cloneNode(deep) { 297 | // call original clone 298 | var n = domCloneNode.call(this, deep); 299 | // upgrade the element and subtree 300 | upgrade(n); 301 | // return the clone 302 | return n; 303 | } 304 | 305 | // capture native createElement before we override it 306 | var domCreateElement = document.createElement.bind(document); 307 | var domCreateElementNS = document.createElementNS.bind(document); 308 | // capture native cloneNode before we override it 309 | var domCloneNode = Node.prototype.cloneNode; 310 | 311 | // Create a custom 'instanceof'. This is necessary when CustomElements 312 | // are implemented via a mixin strategy, as for example on IE10. 313 | var isInstance; 314 | if (!Object.__proto__ && !useNative) { 315 | isInstance = function(obj, ctor) { 316 | var p = obj; 317 | while (p) { 318 | // NOTE: this is not technically correct since we're not checking if 319 | // an object is an instance of a constructor; however, this should 320 | // be good enough for the mixin strategy. 321 | if (p === ctor.prototype) { 322 | return true; 323 | } 324 | p = p.__proto__; 325 | } 326 | return false; 327 | }; 328 | } else { 329 | isInstance = function(obj, base) { 330 | return obj instanceof base; 331 | }; 332 | } 333 | 334 | // exports 335 | document.registerElement = register; 336 | document.createElement = createElement; // override 337 | document.createElementNS = createElementNS; // override 338 | Node.prototype.cloneNode = cloneNode; // override 339 | scope.registry = registry; 340 | scope.instanceof = isInstance; 341 | scope.reservedTagList = reservedTagList; 342 | scope.getRegisteredDefinition = getRegisteredDefinition; 343 | 344 | // bc 345 | document.register = document.registerElement; 346 | 347 | }); 348 | -------------------------------------------------------------------------------- /test/js/customElements.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 The Polymer Authors. All rights reserved. 3 | * Use of this source code is governed by a BSD-style 4 | * license that can be found in the LICENSE file. 5 | */ 6 | 7 | suite('customElements', function() { 8 | var work; 9 | var assert = chai.assert; 10 | var HTMLNS = 'http://www.w3.org/1999/xhtml'; 11 | 12 | setup(function() { 13 | work = document.createElement('div'); 14 | document.body.appendChild(work); 15 | }); 16 | 17 | teardown(function() { 18 | document.body.removeChild(work); 19 | }); 20 | 21 | test('document.registerElement requires name argument', function() { 22 | try { 23 | document.registerElement(); 24 | } catch(x) { 25 | return; 26 | } 27 | assert.ok(false, 'document.registerElement failed to throw when given no arguments'); 28 | }); 29 | 30 | test('document.registerElement requires name argument to contain a dash', function() { 31 | try { 32 | document.registerElement('xfoo', {prototype: Object.create(HTMLElement.prototype)}); 33 | } catch(x) { 34 | return; 35 | } 36 | assert.ok(false, 'document.registerElement failed to throw when given no arguments'); 37 | }); 38 | 39 | // http://w3c.github.io/webcomponents/spec/custom/#extensions-to-document-interface-to-register 40 | test('document.registerElement second argument is optional', function() { 41 | try { 42 | document.registerElement('x-no-proto'); 43 | } catch(x) { 44 | return; 45 | } 46 | assert.ok(true, 'document.registerElement failed to function without ElementRegistionOptions argument'); 47 | }); 48 | 49 | test('document.registerElement requires name argument to not conflict with a reserved name', function() { 50 | try { 51 | document.registerElement('font-face', {prototype: Object.create(HTMLElement.prototype)}); 52 | } catch(x) { 53 | return; 54 | } 55 | assert.ok(false, 'Failed to execute \'registerElement\' on \'Document\': Registration failed for type \'font-face\'. The type name is invalid.'); 56 | }); 57 | 58 | test('document.registerElement requires name argument to be unique', function() { 59 | var proto = {prototype: Object.create(HTMLElement.prototype)}; 60 | document.registerElement('x-duplicate', proto); 61 | try { 62 | document.registerElement('x-duplicate', proto); 63 | } catch(x) { 64 | return; 65 | } 66 | assert.ok(false, 'document.registerElement failed to throw when called multiple times with the same element name'); 67 | }); 68 | 69 | test('document.registerElement create via new', function() { 70 | // register x-foo 71 | var XFoo = document.registerElement('x-foo', {prototype: Object.create(HTMLElement.prototype)}); 72 | // create an instance via new 73 | var xfoo = new XFoo(); 74 | // test localName 75 | assert.equal(xfoo.localName, 'x-foo'); 76 | // attach content 77 | work.appendChild(xfoo).textContent = '[x-foo]'; 78 | // reacquire 79 | var xfoo = work.querySelector('x-foo'); 80 | // test textContent 81 | assert.equal(xfoo.textContent, '[x-foo]'); 82 | }); 83 | 84 | test('document.registerElement create via createElement', function() { 85 | // register x-foo 86 | var XFoo = document.registerElement('x-foo2', {prototype: Object.create(HTMLElement.prototype)}); 87 | // create an instance via createElement 88 | var xfoo = document.createElement('x-foo2'); 89 | // test localName 90 | assert.equal(xfoo.localName, 'x-foo2'); 91 | // attach content 92 | xfoo.textContent = '[x-foo2]'; 93 | // test textContent 94 | assert.equal(xfoo.textContent, '[x-foo2]'); 95 | }); 96 | 97 | test('document.registerElement create via createElementNS', function() { 98 | // create an instance via createElementNS 99 | var xfoo = document.createElementNS(HTMLNS, 'x-foo2'); 100 | // test localName 101 | assert.equal(xfoo.localName, 'x-foo2'); 102 | // attach content 103 | xfoo.textContent = '[x-foo2]'; 104 | // test textContent 105 | assert.equal(xfoo.textContent, '[x-foo2]'); 106 | }); 107 | 108 | test('document.registerElement treats names as case insensitive', function() { 109 | var proto = {prototype: Object.create(HTMLElement.prototype)}; 110 | proto.prototype.isXCase = true; 111 | var XCase = document.registerElement('X-CASE', proto); 112 | // createElement 113 | var x = document.createElement('X-CASE'); 114 | assert.equal(x.isXCase, true); 115 | x = document.createElement('x-case'); 116 | assert.equal(x.isXCase, true); 117 | // createElementNS 118 | // NOTE: createElementNS is case sensitive, disable tests 119 | // x = document.createElementNS(HTMLNS, 'X-CASE'); 120 | // assert.equal(x.isXCase, true); 121 | // x = document.createElementNS(HTMLNS, 'x-case'); 122 | // assert.equal(x.isXCase, true); 123 | // upgrade 124 | work.innerHTML = ''; 125 | CustomElements.takeRecords(); 126 | assert.equal(work.firstChild.isXCase, true); 127 | assert.equal(work.firstChild.nextSibling.isXCase, true); 128 | }); 129 | 130 | test('document.registerElement create multiple instances', function() { 131 | var XFooPrototype = Object.create(HTMLElement.prototype); 132 | XFooPrototype.bluate = function() { 133 | this.color = 'lightblue'; 134 | }; 135 | var XFoo = document.registerElement('x-foo3', { 136 | prototype: XFooPrototype 137 | }); 138 | // create an instance 139 | var xfoo1 = new XFoo(); 140 | // create another instance 141 | var xfoo2 = new XFoo(); 142 | // test textContent 143 | xfoo1.textContent = '[x-foo1]'; 144 | xfoo2.textContent = '[x-foo2]'; 145 | assert.equal(xfoo1.textContent, '[x-foo1]'); 146 | assert.equal(xfoo2.textContent, '[x-foo2]'); 147 | // test bluate 148 | xfoo1.bluate(); 149 | assert.equal(xfoo1.color, 'lightblue'); 150 | assert.isUndefined(xfoo2.color); 151 | }); 152 | 153 | test('document.registerElement extend native element', function() { 154 | // test native element extension 155 | var XBarPrototype = Object.create(HTMLButtonElement.prototype); 156 | var XBar = document.registerElement('x-bar', { 157 | prototype: XBarPrototype, 158 | extends: 'button' 159 | }); 160 | var xbar = new XBar(); 161 | work.appendChild(xbar).textContent = 'x-bar'; 162 | xbar = work.querySelector('button[is=x-bar]'); 163 | assert(xbar); 164 | assert.equal(xbar.textContent, 'x-bar'); 165 | // test extension of native element extension 166 | var XBarBarPrototype = Object.create(XBarPrototype); 167 | var XBarBar = document.registerElement('x-barbar', { 168 | prototype: XBarBarPrototype, 169 | extends: 'button' 170 | }); 171 | var xbarbar = new XBarBar(); 172 | work.appendChild(xbarbar).textContent = 'x-barbar'; 173 | xbarbar = work.querySelector('button[is=x-barbar]'); 174 | assert(xbarbar); 175 | assert.equal(xbarbar.textContent, 'x-barbar'); 176 | // test extension^3 177 | var XBarBarBarPrototype = Object.create(XBarBarPrototype); 178 | var XBarBarBar = document.registerElement('x-barbarbar', { 179 | prototype: XBarBarBarPrototype, 180 | extends: 'button' 181 | }); 182 | var xbarbarbar = new XBarBarBar(); 183 | work.appendChild(xbarbarbar).textContent = 'x-barbarbar'; 184 | xbarbarbar = work.querySelector('button[is=x-barbarbar]'); 185 | assert(xbarbarbar); 186 | assert.equal(xbarbarbar.textContent, 'x-barbarbar'); 187 | }); 188 | 189 | test('document.registerElement createdCallback in prototype', function() { 190 | var XBooPrototype = Object.create(HTMLElement.prototype); 191 | XBooPrototype.createdCallback = function() { 192 | this.style.fontStyle = 'italic'; 193 | } 194 | var XBoo = document.registerElement('x-boo', { 195 | prototype: XBooPrototype 196 | }); 197 | var xboo = new XBoo(); 198 | assert.equal(xboo.style.fontStyle, 'italic'); 199 | // 200 | var XBooBooPrototype = Object.create(XBooPrototype); 201 | XBooBooPrototype.createdCallback = function() { 202 | XBoo.prototype.createdCallback.call(this); 203 | this.style.fontSize = '32pt'; 204 | }; 205 | var XBooBoo = document.registerElement('x-booboo', { 206 | prototype: XBooBooPrototype 207 | }); 208 | var xbooboo = new XBooBoo(); 209 | assert.equal(xbooboo.style.fontStyle, 'italic'); 210 | assert.equal(xbooboo.style.fontSize, '32pt'); 211 | }); 212 | 213 | test('document.registerElement [created|attached|detached]Callbacks in prototype', function(done) { 214 | var ready, inserted, removed; 215 | var XBooPrototype = Object.create(HTMLElement.prototype); 216 | XBooPrototype.createdCallback = function() { 217 | ready = true; 218 | } 219 | XBooPrototype.attachedCallback = function() { 220 | inserted = true; 221 | } 222 | XBooPrototype.detachedCallback = function() { 223 | removed = true; 224 | } 225 | var XBoo = document.registerElement('x-boo-ir', { 226 | prototype: XBooPrototype 227 | }); 228 | var xboo = new XBoo(); 229 | assert(ready, 'ready must be true [XBoo]'); 230 | assert(!inserted, 'inserted must be false [XBoo]'); 231 | assert(!removed, 'removed must be false [XBoo]'); 232 | work.appendChild(xboo); 233 | CustomElements.takeRecords(); 234 | assert(inserted, 'inserted must be true [XBoo]'); 235 | work.removeChild(xboo); 236 | CustomElements.takeRecords(); 237 | assert(removed, 'removed must be true [XBoo]'); 238 | // 239 | ready = inserted = removed = false; 240 | var XBooBooPrototype = Object.create(XBooPrototype); 241 | XBooBooPrototype.createdCallback = function() { 242 | XBoo.prototype.createdCallback.call(this); 243 | }; 244 | XBooBooPrototype.attachedCallback = function() { 245 | XBoo.prototype.attachedCallback.call(this); 246 | }; 247 | XBooBooPrototype.detachedCallback = function() { 248 | XBoo.prototype.detachedCallback.call(this); 249 | }; 250 | var XBooBoo = document.registerElement('x-booboo-ir', { 251 | prototype: XBooBooPrototype 252 | }); 253 | var xbooboo = new XBooBoo(); 254 | assert(ready, 'ready must be true [XBooBoo]'); 255 | assert(!inserted, 'inserted must be false [XBooBoo]'); 256 | assert(!removed, 'removed must be false [XBooBoo]'); 257 | work.appendChild(xbooboo); 258 | CustomElements.takeRecords(); 259 | assert(inserted, 'inserted must be true [XBooBoo]'); 260 | work.removeChild(xbooboo); 261 | CustomElements.takeRecords(); 262 | assert(removed, 'removed must be true [XBooBoo]'); 263 | done(); 264 | }); 265 | 266 | test('document.registerElement attributeChangedCallback in prototype', function(done) { 267 | var XBooPrototype = Object.create(HTMLElement.prototype); 268 | XBooPrototype.attributeChangedCallback = function(inName, inOldValue) { 269 | if (inName == 'foo' && inOldValue=='bar' 270 | && this.attributes.foo.value == 'zot') { 271 | done(); 272 | } 273 | } 274 | var XBoo = document.registerElement('x-boo-acp', { 275 | prototype: XBooPrototype 276 | }); 277 | var xboo = new XBoo(); 278 | xboo.setAttribute('foo', 'bar'); 279 | xboo.setAttribute('foo', 'zot'); 280 | }); 281 | 282 | test('document.registerElement attachedCallbacks in prototype', function(done) { 283 | var inserted = 0; 284 | var XBooPrototype = Object.create(HTMLElement.prototype); 285 | XBooPrototype.attachedCallback = function() { 286 | inserted++; 287 | }; 288 | var XBoo = document.registerElement('x-boo-at', { 289 | prototype: XBooPrototype 290 | }); 291 | var xboo = new XBoo(); 292 | assert.equal(inserted, 0, 'inserted must be 0'); 293 | work.appendChild(xboo); 294 | CustomElements.takeRecords(); 295 | assert.equal(inserted, 1, 'inserted must be 1'); 296 | work.removeChild(xboo); 297 | CustomElements.takeRecords(); 298 | assert(!xboo.parentNode); 299 | work.appendChild(xboo); 300 | CustomElements.takeRecords(); 301 | assert.equal(inserted, 2, 'inserted must be 2'); 302 | done(); 303 | }); 304 | 305 | test('document.registerElement detachedCallbacks in prototype', function(done) { 306 | var ready, inserted, removed; 307 | var XBooPrototype = Object.create(HTMLElement.prototype); 308 | XBooPrototype.detachedCallback = function() { 309 | removed = true; 310 | } 311 | var XBoo = document.registerElement('x-boo-ir2', { 312 | prototype: XBooPrototype 313 | }); 314 | var xboo = new XBoo(); 315 | assert(!removed, 'removed must be false [XBoo]'); 316 | work.appendChild(xboo); 317 | CustomElements.takeRecords(); 318 | work.removeChild(xboo); 319 | CustomElements.takeRecords(); 320 | assert(removed, 'removed must be true [XBoo]'); 321 | // 322 | ready = inserted = removed = false; 323 | var XBooBooPrototype = Object.create(XBooPrototype); 324 | XBooBooPrototype.detachedCallback = function() { 325 | XBoo.prototype.detachedCallback.call(this); 326 | }; 327 | var XBooBoo = document.registerElement('x-booboo-ir2', { 328 | prototype: XBooBooPrototype 329 | }); 330 | var xbooboo = new XBooBoo(); 331 | assert(!removed, 'removed must be false [XBooBoo]'); 332 | work.appendChild(xbooboo); 333 | CustomElements.takeRecords(); 334 | work.removeChild(xbooboo); 335 | CustomElements.takeRecords(); 336 | assert(removed, 'removed must be true [XBooBoo]'); 337 | done(); 338 | }); 339 | 340 | test('document.registerElement can use Functions as definitions', function() { 341 | // function used as Custom Element defintion 342 | function A$A() { 343 | this.alive = true; 344 | } 345 | A$A.prototype = Object.create(HTMLElement.prototype); 346 | // bind createdCallback to function body 347 | A$A.prototype.createdCallback = A$A; 348 | A$A = document.registerElement('a-a', A$A); 349 | // test via new 350 | var a = new A$A(); 351 | assert.equal(a.alive, true); 352 | // test via parser upgrade 353 | work.innerHTML = ''; 354 | CustomElements.takeRecords(); 355 | assert.equal(work.firstElementChild.alive, true); 356 | }); 357 | 358 | test('node.cloneNode upgrades', function(done) { 359 | var XBooPrototype = Object.create(HTMLElement.prototype); 360 | XBooPrototype.createdCallback = function() { 361 | this.__ready__ = true; 362 | }; 363 | var XBoo = document.registerElement('x-boo-clone', { 364 | prototype: XBooPrototype 365 | }); 366 | var xboo = new XBoo(); 367 | work.appendChild(xboo); 368 | CustomElements.takeRecords(); 369 | var xboo2 = xboo.cloneNode(true); 370 | assert(xboo2.__ready__, 'clone createdCallback must be called'); 371 | done(); 372 | }); 373 | 374 | test('entered left apply to view', function() { 375 | var invocations = []; 376 | var elementProto = Object.create(HTMLElement.prototype); 377 | elementProto.createdCallback = function() { 378 | invocations.push('created'); 379 | } 380 | elementProto.attachedCallback = function() { 381 | invocations.push('entered'); 382 | } 383 | elementProto.detachedCallback = function() { 384 | invocations.push('left'); 385 | } 386 | var tagName = 'x-entered-left-view'; 387 | var CustomElement = document.registerElement(tagName, { prototype: elementProto }); 388 | 389 | var docB = document.implementation.createHTMLDocument(''); 390 | docB.body.innerHTML = '<' + tagName + '>'; 391 | CustomElements.upgradeDocumentTree(docB); 392 | CustomElements.takeRecords(); 393 | assert.deepEqual(invocations, ['created'], 'created but not entered view'); 394 | 395 | var element = docB.body.childNodes[0]; 396 | // note, cannot use instanceof due to IE 397 | assert.equal(element.__proto__, CustomElement.prototype, 'element is correct type'); 398 | 399 | work.appendChild(element) 400 | CustomElements.takeRecords(); 401 | assert.deepEqual(invocations, ['created', 'entered'], 402 | 'created and entered view'); 403 | 404 | docB.body.appendChild(element); 405 | CustomElements.takeRecords(); 406 | assert.deepEqual(invocations, ['created', 'entered', 'left'], 407 | 'created, entered then left view'); 408 | }); 409 | 410 | test('attachedCallback ordering', function() { 411 | var log = []; 412 | var p = Object.create(HTMLElement.prototype); 413 | p.attachedCallback = function() { 414 | log.push(this.id); 415 | }; 416 | document.registerElement('x-boo-ordering', {prototype: p}); 417 | 418 | work.innerHTML = 419 | '' + 420 | '' + 421 | '' + 422 | '' + 423 | '' + 424 | '' + 425 | ''; 426 | 427 | CustomElements.takeRecords(); 428 | assert.deepEqual(['a', 'b', 'c', 'd', 'e'], log); 429 | }); 430 | 431 | test('detachedCallback ordering', function() { 432 | var log = []; 433 | var p = Object.create(HTMLElement.prototype); 434 | p.detachedCallback = function() { 435 | log.push(this.id); 436 | }; 437 | document.registerElement('x-boo2-ordering', {prototype: p}); 438 | 439 | work.innerHTML = 440 | '' + 441 | '' + 442 | '' + 443 | '' + 444 | '' + 445 | '' + 446 | ''; 447 | 448 | CustomElements.takeRecords(); 449 | work.removeChild(work.firstElementChild); 450 | CustomElements.takeRecords(); 451 | assert.deepEqual(['a', 'b', 'c', 'd', 'e'], log); 452 | }); 453 | 454 | test('instanceof', function() { 455 | var p = Object.create(HTMLElement.prototype); 456 | var PCtor = document.registerElement('x-instance', {prototype: p}); 457 | var x = document.createElement('x-instance'); 458 | assert.isTrue(CustomElements.instanceof(x, PCtor), 'instanceof failed for x-instance'); 459 | x = document.createElementNS(HTMLNS, 'x-instance'); 460 | assert.isTrue(CustomElements.instanceof(x, PCtor), 'instanceof failed for x-instance'); 461 | 462 | var p2 = Object.create(PCtor.prototype); 463 | var P2Ctor = document.registerElement('x-instance2', {prototype: p2}); 464 | var x2 = document.createElement('x-instance2'); 465 | assert.isTrue(CustomElements.instanceof(x2, P2Ctor), 'instanceof failed for x-instance2'); 466 | assert.isTrue(CustomElements.instanceof(x2, PCtor), 'instanceof failed for x-instance2'); 467 | x2 = document.createElementNS(HTMLNS, 'x-instance2'); 468 | assert.isTrue(CustomElements.instanceof(x2, P2Ctor), 'instanceof failed for x-instance2'); 469 | assert.isTrue(CustomElements.instanceof(x2, PCtor), 'instanceof failed for x-instance2'); 470 | }); 471 | 472 | 473 | test('instanceof typeExtension', function() { 474 | var p = Object.create(HTMLButtonElement.prototype); 475 | var PCtor = document.registerElement('x-button-instance', {prototype: p, extends: 'button'}); 476 | var x = document.createElement('button', 'x-button-instance'); 477 | assert.isTrue(CustomElements.instanceof(x, PCtor), 'instanceof failed for x-button-instance'); 478 | assert.isTrue(CustomElements.instanceof(x, HTMLButtonElement), 'instanceof failed for x-button-instance'); 479 | x = document.createElementNS(HTMLNS, 'button', 'x-button-instance'); 480 | assert.isTrue(CustomElements.instanceof(x, PCtor), 'instanceof failed for x-button-instance'); 481 | assert.isTrue(CustomElements.instanceof(x, HTMLButtonElement), 'instanceof failed for x-button-instance'); 482 | 483 | var p2 = Object.create(PCtor.prototype); 484 | var P2Ctor = document.registerElement('x-button-instance2', {prototype: p2, extends: 'button'}); 485 | var x2 = document.createElement('button','x-button-instance2'); 486 | assert.isTrue(CustomElements.instanceof(x2, P2Ctor), 'instanceof failed for x-button-instance2'); 487 | assert.isTrue(CustomElements.instanceof(x2, PCtor), 'instanceof failed for x-button-instance2'); 488 | assert.isTrue(CustomElements.instanceof(x2, HTMLButtonElement), 'instanceof failed for x-button-instance2'); 489 | x2 = document.createElementNS(HTMLNS, 'button','x-button-instance2'); 490 | assert.isTrue(CustomElements.instanceof(x2, P2Ctor), 'instanceof failed for x-button-instance2'); 491 | assert.isTrue(CustomElements.instanceof(x2, PCtor), 'instanceof failed for x-button-instance2'); 492 | assert.isTrue(CustomElements.instanceof(x2, HTMLButtonElement), 'instanceof failed for x-button-instance2'); 493 | }); 494 | 495 | test('extends and prototype mismatch', function() { 496 | var p = Object.create(HTMLElement.prototype); 497 | var PCtor = document.registerElement('not-button', { 498 | extends: 'button', 499 | prototype: p 500 | }); 501 | 502 | var e = document.createElement('button', 'not-button'); 503 | 504 | // NOTE: firefox has a hack for instanceof that uses element tagname mapping 505 | // Work around by checking prototype manually 506 | // 507 | var ff = document.createElement('button'); ff.__proto__ = null; 508 | if (ff instanceof HTMLButtonElement) { 509 | // Base proto will be one below custom proto 510 | var proto = e.__proto__.__proto__; 511 | assert.isFalse(proto === HTMLButtonElement.prototype); 512 | assert.isTrue(proto === HTMLElement.prototype); 513 | } else { 514 | assert.isFalse(CustomElements.instanceof(e, HTMLButtonElement)); 515 | assert.isTrue(CustomElements.instanceof(e, HTMLElement)); 516 | } 517 | }); 518 | 519 | }); 520 | 521 | htmlSuite('customElements (html)', function() { 522 | htmlTest('html/attributes.html'); 523 | htmlTest('html/upgrade-order.html'); 524 | htmlTest('html/upgrade-dcl.html'); 525 | htmlTest('html/imports.html'); 526 | htmlTest('html/shadowdom.html'); 527 | }); 528 | --------------------------------------------------------------------------------