flutter_flutter/framework/sky-element/element-registry.sky
Elliott Sprehn 5e18888a0b Be more strict about attributes in sky-binder.
Instead of making all the built in attributes be global we match them to the
elements they actually apply to. I also removed a bunch of attributes that no
longer work in Sky (but haven't been removed from HTMLAttributeNames.in) yet,
or which we plan to remove.

I also removed the title property from HTMLElement. The <sky-box> widget forgot
to declare its title attribute and no error was generated because of the title
property existing on all elements. Sky doesn't have this so lets remove the API
now and clean up the C++ later.

R=ojan@chromium.org

Review URL: https://codereview.chromium.org/807243003
2015-01-16 14:41:07 -08:00

166 lines
3.8 KiB
Plaintext

<!--
// 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',
]);
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 (name.startsWith('data-'))
return true;
if (globalAttributesNames.has(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 globalAttributesNames.has(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',
});
module.exports = {
registerElement: registerElement,
getRegistration: getRegistration,
checkAttribute: checkAttribute,
};
</script>