blob: ba317a808c18719a32f1f5515400d7b01540083f [file] [log] [blame]
<!--
// 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>