| OLD | NEW |
| (Empty) |
| 1 <!-- | |
| 2 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 3 // Use of this source code is governed by a BSD-style license that can be | |
| 4 // found in the LICENSE file. | |
| 5 --> | |
| 6 <import src="sky-binder.sky" as="binder" /> | |
| 7 <script> | |
| 8 // TODO(esprehn): It would be nice if these were exposed by the platform so | |
| 9 // the framework didn't need to hard code a list. | |
| 10 var globalAttributesNames = new Set([ | |
| 11 'class', | |
| 12 'contenteditable', | |
| 13 'dir', | |
| 14 'id', | |
| 15 'lang', | |
| 16 'spellcheck', | |
| 17 'tabindex', | |
| 18 'style', | |
| 19 ]); | |
| 20 | |
| 21 function isExpandableAttribute(name) { | |
| 22 return name.startsWith('data-') || name.startsWith('on-'); | |
| 23 } | |
| 24 | |
| 25 function isGlobalAttribute(name) { | |
| 26 if (isExpandableAttribute(name)) | |
| 27 return true; | |
| 28 return globalAttributesNames.has(name); | |
| 29 } | |
| 30 | |
| 31 var attributeConverters = { | |
| 32 boolean: function(value) { | |
| 33 if (typeof value == 'string') | |
| 34 return value == 'true'; | |
| 35 return !!value; | |
| 36 }, | |
| 37 number: function(value) { | |
| 38 return Number(value); | |
| 39 }, | |
| 40 string: function(value) { | |
| 41 if (value === null) | |
| 42 return ''; | |
| 43 return String(value); | |
| 44 }, | |
| 45 }; | |
| 46 | |
| 47 function eventHandlerCallback(event) { | |
| 48 var element = event.currentTarget; | |
| 49 var registration = getRegistration(element.localName); | |
| 50 var method = registration.eventHandlers.get(event.type); | |
| 51 var handler = element[method]; | |
| 52 if (!(typeof handler == 'function')) { | |
| 53 throw new Error('Element ' + element.localName + | |
| 54 ' specifies invalid event handler "' + method + '"'); | |
| 55 } | |
| 56 handler.call(element, event); | |
| 57 } | |
| 58 | |
| 59 class ElementRegistration { | |
| 60 constructor(tagName) { | |
| 61 this.tagName = tagName; | |
| 62 this.attributes = new Map(); | |
| 63 this.eventHandlers = new Map(); | |
| 64 this.template = null; | |
| 65 Object.preventExtensions(this); | |
| 66 } | |
| 67 | |
| 68 allowsAttribute(name) { | |
| 69 if (isGlobalAttribute(name)) | |
| 70 return true; | |
| 71 if (this.attributes.has(name)) | |
| 72 return true; | |
| 73 return false; | |
| 74 } | |
| 75 | |
| 76 defineAttribute(name, type) { | |
| 77 var converter = attributeConverters[type]; | |
| 78 | |
| 79 if (!converter) { | |
| 80 console.error('Invalid attribute type "' + type + '", type must be one' | |
| 81 + ' of boolean, number or string.'); | |
| 82 return; | |
| 83 } | |
| 84 | |
| 85 this.attributes.set(name, converter); | |
| 86 } | |
| 87 | |
| 88 synthesizeAttributes(prototype) { | |
| 89 this.attributes.forEach(function(converter, name) { | |
| 90 Object.defineProperty(prototype, name, { | |
| 91 get: function() { | |
| 92 return converter(this.getAttribute(name)); | |
| 93 }, | |
| 94 set: function(newValue) { | |
| 95 this.setAttribute(name, converter(newValue)); | |
| 96 }, | |
| 97 enumerable: true, | |
| 98 configurable: true, | |
| 99 }); | |
| 100 }); | |
| 101 } | |
| 102 | |
| 103 addInstanceEventListeners(instance) { | |
| 104 for (var eventName of this.eventHandlers.keys()) { | |
| 105 instance.addEventListener(eventName, eventHandlerCallback); | |
| 106 } | |
| 107 } | |
| 108 } | |
| 109 | |
| 110 var registrations = new Map(); | |
| 111 | |
| 112 function registerElement(tagName, attributes) { | |
| 113 if (registrations.has(tagName)) | |
| 114 throw new Error('tagName "' + tagName + '" registered twice.'); | |
| 115 var registration = new ElementRegistration(tagName); | |
| 116 for (var name in attributes) { | |
| 117 registration.defineAttribute(name, attributes[name]); | |
| 118 } | |
| 119 registrations.set(tagName, registration); | |
| 120 return registration; | |
| 121 } | |
| 122 | |
| 123 function getRegistration(tagName) { | |
| 124 return registrations.get(tagName); | |
| 125 } | |
| 126 | |
| 127 function checkAttribute(tagName, attrName) { | |
| 128 var registration = getRegistration(tagName); | |
| 129 | |
| 130 if (!registration) | |
| 131 return isGlobalAttribute(attrName); | |
| 132 | |
| 133 return registration.allowsAttribute(attrName); | |
| 134 } | |
| 135 | |
| 136 registerElement('img', { | |
| 137 'width': 'number', | |
| 138 'height': 'number', | |
| 139 // TODO(esprehn): Sky probably doesn't want the crossorign attr. | |
| 140 'crossorigin': 'string', | |
| 141 'src': 'string', | |
| 142 }); | |
| 143 | |
| 144 registerElement('import', { | |
| 145 'as': 'string', | |
| 146 'src': 'string', | |
| 147 'async': 'boolean', | |
| 148 }); | |
| 149 | |
| 150 registerElement('a', { | |
| 151 'href': 'string', | |
| 152 'async': 'boolean', | |
| 153 }); | |
| 154 | |
| 155 registerElement('content', { | |
| 156 'select': 'string', | |
| 157 }); | |
| 158 | |
| 159 registerElement('style', { | |
| 160 'media': 'string', | |
| 161 }); | |
| 162 | |
| 163 registerElement('template', { | |
| 164 'if': 'string', | |
| 165 'repeat': 'string', | |
| 166 }); | |
| 167 | |
| 168 registerElement('iframe', { | |
| 169 'src': 'string', | |
| 170 }); | |
| 171 | |
| 172 module.exports = { | |
| 173 registerElement: registerElement, | |
| 174 getRegistration: getRegistration, | |
| 175 checkAttribute: checkAttribute, | |
| 176 isGlobalAttribute: isGlobalAttribute, | |
| 177 isExpandableAttribute: isExpandableAttribute, | |
| 178 }; | |
| 179 </script> | |
| OLD | NEW |