| <!-- |
| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| --> |
| <import src="sky-binder.sky" as="binder" /> |
| <script> |
| // TODO(esprehn): It would be nice if these were exposed by the platform so |
| // the framework didn't need to hard code a list. |
| var globalAttributesNames = new Set([ |
| 'class', |
| 'contenteditable', |
| 'dir', |
| 'id', |
| 'lang', |
| 'spellcheck', |
| 'tabindex', |
| 'style', |
| ]); |
| |
| function isExpandableAttribute(name) { |
| return name.startsWith('data-') || name.startsWith('on-'); |
| } |
| |
| function isGlobalAttribute(name) { |
| if (isExpandableAttribute(name)) |
| return true; |
| return globalAttributesNames.has(name); |
| } |
| |
| var attributeConverters = { |
| boolean: function(value) { |
| if (typeof value == 'string') |
| return value == 'true'; |
| return !!value; |
| }, |
| number: function(value) { |
| return Number(value); |
| }, |
| string: function(value) { |
| if (value === null) |
| return ''; |
| return String(value); |
| }, |
| }; |
| |
| function eventHandlerCallback(event) { |
| var element = event.currentTarget; |
| var registration = getRegistration(element.localName); |
| var method = registration.eventHandlers.get(event.type); |
| var handler = element[method]; |
| if (!(typeof handler == 'function')) { |
| throw new Error('Element ' + element.localName + |
| ' specifies invalid event handler "' + method + '"'); |
| } |
| handler.call(element, event); |
| } |
| |
| class ElementRegistration { |
| constructor(tagName) { |
| this.tagName = tagName; |
| this.attributes = new Map(); |
| this.eventHandlers = new Map(); |
| this.template = null; |
| Object.preventExtensions(this); |
| } |
| |
| allowsAttribute(name) { |
| if (isGlobalAttribute(name)) |
| return true; |
| if (this.attributes.has(name)) |
| return true; |
| return false; |
| } |
| |
| defineAttribute(name, type) { |
| var converter = attributeConverters[type]; |
| |
| if (!converter) { |
| console.error('Invalid attribute type "' + type + '", type must be one' |
| + ' of boolean, number or string.'); |
| return; |
| } |
| |
| this.attributes.set(name, converter); |
| } |
| |
| synthesizeAttributes(prototype) { |
| this.attributes.forEach(function(converter, name) { |
| Object.defineProperty(prototype, name, { |
| get: function() { |
| return converter(this.getAttribute(name)); |
| }, |
| set: function(newValue) { |
| this.setAttribute(name, converter(newValue)); |
| }, |
| enumerable: true, |
| configurable: true, |
| }); |
| }); |
| } |
| |
| addInstanceEventListeners(instance) { |
| for (var eventName of this.eventHandlers.keys()) { |
| instance.addEventListener(eventName, eventHandlerCallback); |
| } |
| } |
| } |
| |
| var registrations = new Map(); |
| |
| function registerElement(tagName, attributes) { |
| if (registrations.has(tagName)) |
| throw new Error('tagName "' + tagName + '" registered twice.'); |
| var registration = new ElementRegistration(tagName); |
| for (var name in attributes) { |
| registration.defineAttribute(name, attributes[name]); |
| } |
| registrations.set(tagName, registration); |
| return registration; |
| } |
| |
| function getRegistration(tagName) { |
| return registrations.get(tagName); |
| } |
| |
| function checkAttribute(tagName, attrName) { |
| var registration = getRegistration(tagName); |
| |
| if (!registration) |
| return isGlobalAttribute(attrName); |
| |
| return registration.allowsAttribute(attrName); |
| } |
| |
| registerElement('img', { |
| 'width': 'number', |
| 'height': 'number', |
| // TODO(esprehn): Sky probably doesn't want the crossorign attr. |
| 'crossorigin': 'string', |
| 'src': 'string', |
| }); |
| |
| registerElement('import', { |
| 'as': 'string', |
| 'src': 'string', |
| 'async': 'boolean', |
| }); |
| |
| registerElement('a', { |
| 'href': 'string', |
| 'async': 'boolean', |
| }); |
| |
| registerElement('content', { |
| 'select': 'string', |
| }); |
| |
| registerElement('style', { |
| 'media': 'string', |
| }); |
| |
| registerElement('template', { |
| 'if': 'string', |
| 'repeat': 'string', |
| }); |
| |
| registerElement('iframe', { |
| 'src': 'string', |
| }); |
| |
| module.exports = { |
| registerElement: registerElement, |
| getRegistration: getRegistration, |
| checkAttribute: checkAttribute, |
| isGlobalAttribute: isGlobalAttribute, |
| isExpandableAttribute: isExpandableAttribute, |
| }; |
| </script> |