diff --git a/src/CustomElements.js b/src/CustomElements.js index dbe4acc..d0b91b0 100644 --- a/src/CustomElements.js +++ b/src/CustomElements.js @@ -56,23 +56,23 @@ if (useNative) { * * When a registered element is created, a `readyCallback` method is called * in the scope of the element. The `readyCallback` method can be specified on - * either `inOptions.prototype` or `inOptions.lifecycle` with the latter taking + * either `options.prototype` or `options.lifecycle` with the latter taking * precedence. * * @method register - * @param {String} inName The tag name to register. Must include a dash ('-'), + * @param {String} name The tag name to register. Must include a dash ('-'), * for example 'x-component'. - * @param {Object} inOptions - * @param {String} [inOptions.extends] + * @param {Object} options + * @param {String} [options.extends] * (_off spec_) Tag name of an element to extend (or blank for a new * element). This parameter is not part of the specification, but instead * is a hint for the polyfill because the extendee is difficult to infer. * Remember that the input prototype must chain to the extended element's * prototype (or HTMLElement.prototype) regardless of the value of * `extends`. - * @param {Object} inOptions.prototype The prototype to use for the new + * @param {Object} options.prototype The prototype to use for the new * element. The prototype must inherit from HTMLElement. - * @param {Object} [inOptions.lifecycle] + * @param {Object} [options.lifecycle] * Callbacks that fire at important phases in the life of the custom * element. * @@ -89,18 +89,23 @@ if (useNative) { * }); * @return {Function} Constructor for the newly registered type. */ - function register(inName, inOptions) { - //console.warn('document.register("' + inName + '", ', inOptions, ')'); + function register(name, options) { + //console.warn('document.register("' + name + '", ', options, ')'); // construct a defintion out of options - // TODO(sjmiles): probably should clone inOptions instead of mutating it - var definition = inOptions || {}; - if (!inName) { + // TODO(sjmiles): probably should clone options instead of mutating it + var definition = options || {}; + if (!name) { // TODO(sjmiles): replace with more appropriate error (EricB can probably // offer guidance) - throw new Error('Name argument must not be empty'); + throw new Error('document.register: first argument `name` must not be empty'); + } + if (name.indexOf('-') < 0) { + // TODO(sjmiles): replace with more appropriate error (EricB can probably + // offer guidance) + throw new Error('document.register: first argument `name` must contain a dash (\'-\'). Argument was \'' + String(name) + '\'.'); } // record name - definition.name = inName; + definition.name = name; // must have a prototype, default to an extension of HTMLElement // TODO(sjmiles): probably should throw if no prototype, check spec if (!definition.prototype) { @@ -123,7 +128,7 @@ if (useNative) { // overrides to implement attributeChanged callback overrideAttributeApi(definition.prototype); // 7.1.5: Register the DEFINITION with DOCUMENT - registerDefinition(inName, definition); + registerDefinition(name, definition); // 7.1.7. Run custom element constructor generation algorithm with PROTOTYPE // 7.1.8. Return the output of the previous step. definition.ctor = generateConstructor(definition); @@ -138,40 +143,40 @@ if (useNative) { return definition.ctor; } - function ancestry(inExtends) { - var extendee = registry[inExtends]; + function ancestry(extnds) { + var extendee = registry[extnds]; if (extendee) { return ancestry(extendee.extends).concat([extendee]); } return []; } - function resolveTagName(inDefinition) { + function resolveTagName(definition) { // if we are explicitly extending something, that thing is our // baseTag, unless it represents a custom component - var baseTag = inDefinition.extends; + var baseTag = definition.extends; // if our ancestry includes custom components, we only have a // baseTag if one of them does - for (var i=0, a; (a=inDefinition.ancestry[i]); i++) { + for (var i=0, a; (a=definition.ancestry[i]); i++) { baseTag = a.is && a.tag; } // our tag is our baseTag, if it exists, and otherwise just our name - inDefinition.tag = baseTag || inDefinition.name; + definition.tag = baseTag || definition.name; if (baseTag) { // if there is a base tag, use secondary 'is' specifier - inDefinition.is = inDefinition.name; + definition.is = definition.name; } } - function resolvePrototypeChain(inDefinition) { + function resolvePrototypeChain(definition) { // if we don't support __proto__ we need to locate the native level // prototype for precise mixing in if (!Object.__proto__) { // default prototype var native = HTMLElement.prototype; // work out prototype when using type-extension - if (inDefinition.is) { - var inst = document.createElement(inDefinition.tag); + if (definition.is) { + var inst = document.createElement(definition.tag); native = Object.getPrototypeOf(inst); } // ensure __proto__ reference is installed at each point on the prototype @@ -179,7 +184,7 @@ if (useNative) { // NOTE: On platforms without __proto__, a mixin strategy is used instead // of prototype swizzling. In this case, this generated __proto__ provides // limited support for prototype traversal. - var proto = inDefinition.prototype, ancestor; + var proto = definition.prototype, ancestor; while (proto && (proto !== native)) { var ancestor = Object.getPrototypeOf(proto); proto.__proto__ = ancestor; @@ -187,49 +192,49 @@ if (useNative) { } } // cache this in case of mixin - inDefinition.native = native; + definition.native = native; } // SECTION 4 - function instantiate(inDefinition) { + function instantiate(definition) { // 4.a.1. Create a new object that implements PROTOTYPE // 4.a.2. Let ELEMENT by this new object // // the custom element instantiation algorithm must also ensure that the // output is a valid DOM element with the proper wrapper in place. // - return upgrade(domCreateElement(inDefinition.tag), inDefinition); + return upgrade(domCreateElement(definition.tag), definition); } - function upgrade(inElement, inDefinition) { + function upgrade(element, definition) { // some definitions specify an 'is' attribute - if (inDefinition.is) { - inElement.setAttribute('is', inDefinition.is); + if (definition.is) { + element.setAttribute('is', definition.is); } - // make 'element' implement inDefinition.prototype - implement(inElement, inDefinition); + // make 'element' implement definition.prototype + implement(element, definition); // flag as upgraded - inElement.__upgraded__ = true; - // there should never be a shadow root on inElement at this point + element.__upgraded__ = true; + // there should never be a shadow root on element at this point // we require child nodes be upgraded before `created` - scope.upgradeSubtree(inElement); + scope.upgradeSubtree(element); // lifecycle management - created(inElement); + created(element); // OUTPUT - return inElement; + return element; } - function implement(inElement, inDefinition) { + function implement(element, definition) { // prototype swizzling is best if (Object.__proto__) { - inElement.__proto__ = inDefinition.prototype; + element.__proto__ = definition.prototype; } else { // where above we can re-acquire inPrototype via // getPrototypeOf(Element), we cannot do so when // we use mixin, so we install a magic reference - customMixin(inElement, inDefinition.prototype, inDefinition.native); - inElement.__proto__ = inDefinition.prototype; + customMixin(element, definition.prototype, definition.native); + element.__proto__ = definition.prototype; } } @@ -257,10 +262,10 @@ if (useNative) { } } - function created(inElement) { + function created(element) { // invoke createdCallback - if (inElement.createdCallback) { - inElement.createdCallback(); + if (element.createdCallback) { + element.createdCallback(); } } @@ -293,13 +298,13 @@ if (useNative) { var registry = {}; - function registerDefinition(inName, inDefinition) { - registry[inName] = inDefinition; + function registerDefinition(name, definition) { + registry[name] = definition; } - function generateConstructor(inDefinition) { + function generateConstructor(definition) { return function() { - return instantiate(inDefinition); + return instantiate(definition); }; } @@ -313,11 +318,11 @@ if (useNative) { return domCreateElement(tag); } - function upgradeElement(inElement) { - if (!inElement.__upgraded__ && (inElement.nodeType === Node.ELEMENT_NODE)) { - var type = inElement.getAttribute('is') || inElement.localName; + function upgradeElement(element) { + if (!element.__upgraded__ && (element.nodeType === Node.ELEMENT_NODE)) { + var type = element.getAttribute('is') || element.localName; var definition = registry[type]; - return definition && upgrade(inElement, definition); + return definition && upgrade(element, definition); } } @@ -353,7 +358,7 @@ if (useNative) { * if it matches no registered custom tag name. * * @method ugprade - * @param {Element} inElement The element to upgrade. + * @param {Element} element The element to upgrade. * @return {Element} The upgraded element. */ scope.upgrade = upgradeElement; diff --git a/test/js/customElements.js b/test/js/customElements.js index c9f3514..9bb7c3a 100644 --- a/test/js/customElements.js +++ b/test/js/customElements.js @@ -17,6 +17,24 @@ document.body.removeChild(work); }); + test('document.register requires name argument', function() { + try { + document.register(); + } catch(x) { + return; + } + assert.ok(false, 'document.register failed to throw when given no arguments'); + }); + + test('document.register requires name argument to contain a dash', function() { + try { + document.register('xfoo', {prototype: Object.create(HTMLElement.prototype)}); + } catch(x) { + return; + } + assert.ok(false, 'document.register failed to throw when given no arguments'); + }); + test('document.register create via new', function() { // register x-foo var XFoo = document.register('x-foo', {prototype: Object.create(HTMLElement.prototype)});