Specs: Element registration in the Dart world

Review URL: https://codereview.chromium.org/908983002
This commit is contained in:
Hixie 2015-02-09 16:10:41 -08:00
parent 50527025ca
commit 99be7bd226
3 changed files with 133 additions and 80 deletions

View File

@ -79,16 +79,26 @@ class Attr {
// @tagname annotation for registering elements
// only useful when placed on classes that inherit from Element
class tagname {
const tagname(this.value);
@nonnull final String value;
class tagname extends AutomaticMetadata {
const tagname(this.name);
@nonnull final String name;
void init(DeclarationMirror target, Module module) {
assert(target is ClassMirror);
if (!target.isSubclassOf(reflectClass(Element)))
throw Error('@tagname can only be used on descendants of Element');
module.registerElement(name, (target as ClassMirror).reflectedType);
}
}
abstract class FindRoot { }
abstract class Element extends ParentNode with ChildNode implements FindRoot {
Element({Map</*@nonnull*/ String, /*@nonnull*/ String> attributes: null,
List</*nonnull*/ ChildNode> nodes: null}); // O(M+N), M = number of attributes, N = number of nodes plus all their descendants
external Element({Map</*@nonnull*/ String, /*@nonnull*/ String> attributes: null,
List</*nonnull*/ ChildNode> nodes: null,
Module hostModule: null}); // O(M+N), M = number of attributes, N = number of nodes plus all their descendants
// initialises the internal attributes table
// appends the given child nodes
// if this.needsShadow, creates a shadow tree
@nonnull String get tagName { // O(N) in number of annotations on the class
// throws a StateError if the class doesn't have an @tagname annotation
@ -104,6 +114,7 @@ abstract class Element extends ParentNode with ChildNode implements FindRoot {
// Returns a new Array and new Attr instances every time.
@nonnull external List<Attr> getAttributes(); // O(N) in number of attributes
get bool needsShadow => false; // O(1)
external ShadowRoot get shadowRoot; // O(1)
// returns the shadow root
// TODO(ianh): Should this be mutable? It would help explain how it gets set...

View File

@ -55,98 +55,65 @@ that were declared by that module except the aforementioned element
tree library.
At the end of the ``<script>`` block's source, if it parsed correctly
and completely, the following code is appended:
and completely, the conceptual equivalent of the following code is
appended (but without affecting the library's list of declarations and
without any possibility of it clashing with identifiers described in
the library itself):
```dart
class _ { }
module.registerElements(reflectClass(_).owner);
void main() {
LibraryMirror library = reflectClass(_).owner as LibraryMirror;
if (library.declarations.containsKey(#init) && library.declarations[#init] is MethodMirror)
init();
module.init(library);
}
```
Then, that ``main()`` function is called.
TODO(ianh): decide what URL and name we should give the libraries, as
exposed in MirrorSystem.getName(libraryMirror.qualifiedName) etc
The ``Module`` class is defined in ``sky:core`` as follows:
```dart
TODO(ianh): dartification of the rest of this file
abstract class AbstractModule extends EventTarget {
AbstractModule({this.document, this.url});
### Exporting element definitions ###
final Document document; // O(1)
// the Document of the module or application
When importing a module into another, Sky runs the following steps:
- let export be the imported module's ``exports`` value
- try to import export
- if that fails:
- try to import each property of export
final String url;
"Try to import" a value means to run the following steps:
- if the value is an element constructor (generated by
``registerElement()``), call this importer module's
``registerElement()`` with the value
@nonnull external Future<@nonnull Module> import(String url); // O(Yikes)
// load and return the URL at the given Module
// if it's already loaded, the future will resolve immediately
// if loading fails, the future will have an error
### IDL ###
@nonnull List</*@nonnull*/ Module> getImports(); // O(N)
// returns the Module objects of all the imported modules
```javascript
dictionary InternalElementOptions {
String tagName;
Boolean shadow = false;
Object prototype = Element;
}
interface InternalElementConstructorWithoutShadow {
constructor (Module hostModule);
attribute String tagName;
}
interface InternalElementConstructorWithShadow {
constructor (Module hostModule);
attribute String tagName;
attribute Boolean shadow;
}
typedef ElementRegistrationOptions (InternalElementOptions or
InternalElementConstructorWithoutShadow or
InternalElementConstructorWithShadow);
external registerElement(@nonnull String tagname, @nonnull Type elementClass); // O(1)
// registers a tag name with the parser
// only useful during parse time
// verify that tagname isn't null or empty
// verify that elementClass is the Type of a class that extends Element (directly or indirectly, but not via "implements" or "with")
// (see the @tagname code for an example of how to verify that from dart)
// verify that there's not already a class registered for this tag name
// if there is, then mark this tagname is broken, so that it acts as if it's not registered in the parser,
// and, if this is the first time it was marked broken, log a console message regarding the issue
// (mention the tag name but not the classes, so that it's not observable that this currently happens out of order)
abstract class AbstractModule : EventTarget {
readonly attribute Document document; // O(1) // the Document of the module or application
Promise<any> import(String url); // O(Yikes) // returns the module's exports
private Array<Module> getImports(); O(N) // returns the Module objects of all the imported modules
readonly attribute String url;
ElementConstructor registerElement(Object options); // O(1)
// if you call registerElement() with an object that was created by
// registerElement(), it just returns the object after registering it,
// rather than creating a new constructor
// otherwise, it proceeds as follows:
// - if options is a Function (i.e. it is either an
// InternalElementConstructorWithoutShadow object or an
// InternalElementConstructorWithShadow object), then let
// constructor be that function, and let prototype be that
// functions's prototype; otherwise, let constructor be a no-op
// function and let prototype be the prototype property of the
// object passed in (the InternalElementOptions; prototype
// defaults to Element).
// - let shadow be option's shadow property's value coerced to a
// boolean, if the property is present, or else the value false.
// - let tagName be option's tagName property's value.
// - create a new Function that acts as if it had the signature of
// the constructors in the ElementConstructor interface, and that
// runs the follows steps when called:
// - throw if not called as a constructor
// - create an actual element object (the C++-backed object)
// called tagName, along with the specified attributes
// - initialise the shadow tree if shadow is true
// - call constructor, if it's not null, with the module
// within which the new element is being constructed as the
// argument
// - append all the specified children
// - mark that new Function as created by registerElement() so that
// it can be recognised if used as an argument to
// registerElement()
// - let that new Function's prototype be the aforementioned prototype
// - let that new Function have tagName and shadow properties set to
// the aforementioned tagName and shadow
// - register the new tagName with this constructor
// - return the new Function (which is, not coincidentally, an
// InternalElementConstructorWithShadow)
void init(LibraryMirror library) {
library.declarations.forEach((Symbol s, DeclarationMirror d) {
d.metadata.forEach((InstanceMirror i) {
if (i.reflectee is AutomaticMetadata)
i.reflectee.init(d, this);
});
});
}
}
class Module : AbstractModule {

View File

@ -30,3 +30,78 @@ The ``@nonnull`` annotation does nothing in code not marked
``external``, but it has been included anyway for documentation
purposes. It indicates places where providing a null is a contract
violation and that results are therefore likely to be poor.
The following definitions are exposed in ``sky:core``:
```dart
abstract class AutomaticMetadata {
const AutomaticMetadata();
void init(DeclarationMirror target) { }
}
/*
class AutomaticFunction extends AutomaticMetadata {
const AutomaticFunction();
void init(DeclarationMirror target) {
assert(target is MethodMirror);
MethodMirror f = target as MethodMirror;
assert(!f.isAbstract);
assert(f.isRegularMethod);
assert(f.isTopLevel);
assert(f.isStatic);
assert(f.parameters.length == 0);
assert(f.returnType == currentMirrorSystem().voidType);
(f.owner as LibraryMirror).invoke(f.simpleName, []);
}
}
const autorun = const AutomaticFunction();
*/
```
Extensions
----------
The following as-yet unimplemented features of the Dart language are
assumed to exist:
* It is assumed that a subclass can define a constructor by reference
to a superclass' constructor, wherein the subclass' constructor has
the same arguments as the superclass' constructor and does nothing
but invoke that superclass' constructor with the same arguments. The
syntax for defining this is, within the class body for a class
called ClassName:
```dart
ClassName = SuperclassName;
ClassName.namedConstructor = SuperclassName.otherNamedConstructor;
```
* It is assumed that the standard library includes something that
matches this pattern:
```dart
class DispatcherController<T> {
Dispatcher<T> _dispatcher;
Dispatcher<T> get dispatcher => _dispatcher;
void add(T data) {
// ...
}
}
typedef bool Filter<T>(T t);
typedef void Handler<T>(T t);
class Dispatcher<T> {
Dispatcher<T> where(Filter<T> filter) { /*...*/ return this; }
void listen(Handler<T> handler) { /* ... */ }
}
class ExceptionListException<T> extends Exception with IterableMixin<T> {
List<T> _exceptions;
void add(T exception) {
if (_exceptions == null)
_exceptions = new List<T>();
_exceptions.add(exception);
}
int get length => _exceptions == null ? 0 : _exceptions.length;
Iterator<T> iterator() => _exceptions.iterator();
}
```