Sky DOM APIs ============ ```dart // ELEMENT TREE API abstract class Node extends EventTarget { @override external List getEventDispatchChain(); // O(N) in number of ancestors across shadow trees // implements EventTarget.getEventDispatchChain() // returns the event dispatch chain (including handling shadow trees) external Root get owner; // O(1) external ParentNode get parentNode; // O(1) Element get parentElement { if (parentNode is Element) return parentNode as Element; return null; } external Node get previousSibling; // O(1) Element get previousElementSibling { var result = previousSibling; while (result != null && result is! Element) result = result.previousSibling; return result as Element; } external Node get nextSibling; // O(1) Element get nextElementSibling { var result = nextSibling; while (result != null && result is! Element) result = result.nextSibling; return result as Element; } // TODO(ianh): rename insertBefore() and insertAfter() since the Web // has an insertBefore() that means something else. What's a good // name, though? external void _insertBefore(Node node); // O(N) in number of descendants // node must be Text or Element, parentNode must be non-null void insertBefore(List nodes) { List.forEach((node) { if (node is String) node = new Text(node); _insertBefore(node); }); } external void _insertAfter(Node node); // O(N) in number of arguments plus all their descendants // node must be Text or Element, parentNode must be non-null void insertAfter(List nodes) { var lastNode = this; List.forEach((node) { if (node is String) node = new Text(node); lastNode._insertAfter(node); lastNode = node; }); } void replaceWith(List nodes) { if (nextSibling != null) { var anchor = nextSibling; remove(); // parentNode can't be null here, so this won't throw anchor.insertBefore(nodes); } else { var anchor = parentNode; remove(); // throws if parentNode is null anchor.append(nodes); } } external void remove(); // O(N) in number of descendants // parentNode must be non-null // called when parentNode changes // this is why insertBefore(), append(), et al, are O(N) -- the whole affected subtree is walked // mutating the element tree from within this is strongly discouraged, since it will result in the // callbacks being invoked while the element tree is in a different state than implied by the callbacks external void parentChangedCallback(ParentNode oldParent, ParentNode newParent); // O(N) in descendants // default implementation calls attached/detached void attachedCallback() { } void detachedCallback() { } external List getDestinationInsertionPoints(); // O(N) in number of insertion points the node is in // returns the elements to which this element was distributed external Node cloneNode({bool deep: false}); // O(1) if deep=false, O(N) in the number of descendants if deep=true external ElementStyleDeclarationList get style; // O(1) // for nodes that aren't in the ApplicationRoot's composed tree, // returns null (so in particular orphaned subtrees and nodes in // module Roots don't have one, nor do shadow tree Roots) // also always returns null for ContentElement elements // -- should be (lazily) updated when the node's parent chain // changes (same time as, e.g., the id hashtable is marked // dirty) external RenderNode get renderNode; // O(1) // this will be null until the first time it is rendered // it becomes null again when it is taken out of the rendering (see style.md) Type getLayoutManager() => null; // O(1) void resetLayoutManager() { // O(1) if (renderNode != null) { renderNode._layoutManager = null; renderNode._needsManager = true; } } } abstract class ParentNode extends Node { external Node get firstChild; // O(1) Element get firstElementChild { var result = firstChild; while (result != null && result is! Element) result = result.nextSibling; return result as Element; } external Node get lastChild; // O(1) Element get lastElementChild { var result = lastChild; while (result != null && result is! Element) result = result.previousSibling; return result as Element; } // Returns a new List every time. external List getChildren(); // O(N) in number of child nodes List getChildElements() { // that the following works without a cast is absurd return getChildren().where((node) => node is Element).toList(); } external void _appendChild(Node node); // O(N) in number of descendants // node must be Text or Element void appendChild(node) { if (node is String) node = new Text(node); _appendChild(node); } void append(List nodes) { nodes.forEach(appendChild); } external void _prependChild(Node node); // O(N) in number of descendants // node must be Text or Element void prependChild(node) { if (node is String) node = new Text(node); _prependChild(node); } void prepend(List nodes) { // note: not implemented in terms of _prependChild() if (firstChild != null) firstChild.insertBefore(nodes); else append(nodes); } external void removeChildren(); // O(N) in number of descendants void setChild(node) { removeChildren(); appendChild(node); } void setChildren(List nodes) { removeChildren(); append(nodes); } } class Attr { const Attr (this.name, [this.value = '']); // O(1) final String name; // O(1) final String value; // O(1) } // @hasShadow annotation for registering elements class _HasShadow { const _HasShadow(); } const hasShadow = const _HasShadow(); abstract class Element extends ParentNode { Element({Map attributes: null, List children: null, Module hostModule: null}) { // O(M+N), M = number of attributes, N = number of children nodes plus all their descendants var shadowClass = reflectClass(hasShadow.runtimeType); var shadowAnnotations = reflect(this).type.metadata.where((mirror) => mirror.type == shadowClass); if (shadowAnnotations.length > 2) throw new StateError('@hasShadow specified multiple times on ' + currentMirrorSystem().getName(reflectClass(this.runtimeType).simpleName)); bool needsShadow = shadowAnnotations.length == 1; if (children != null) children = children.map((node) => node is String ? new Text(node) : node).toList(); this._initElement(attributes, children, hostModule, needsShadow); } external void _initElement(Map attributes, List children, Module hostModule, bool needsShadow); // initialises the internal attributes table, which is a ordered list // appends the given children nodes // children must be Text or Element // if needsShadow is true, creates a shadow tree external bool hasAttribute(String name); // O(N) in number of attributes external String getAttribute(String name); // O(N) in number of attributes external void setAttribute(String name, [String value = '']); // O(N) in number of attributes external void removeAttribute(String name); // O(N) in number of attributes // calling setAttribute() with a null value removes the attribute // (calling it without a value sets it to the empty string) // Returns a new Array and new Attr instances every time. external List getAttributes(); // O(N) in number of attributes external Root get shadowRoot; // O(1) // returns the shadow root void endTagParsedCallback() { } void attributeChangedCallback(String name, String oldValue, String newValue) { } // name will never be null when this is called by sky // TODO(ianh): does a node ever need to know when it's been redistributed? @override Type getLayoutManager() { // O(1) if (renderNode) return renderNode.getProperty(phDisplay); return super.getLayoutManager(); } } class Text extends Node { external Text([String value = '']); // O(1) external String get value; // O(1) external void set (String value); // O(1) void valueChangedCallback(String oldValue, String newValue) { } @override Type getLayoutManager() => TextLayoutManager; // O(1) } class Fragment extends ParentNode { Fragment({List children}); // O(N) in number of arguments plus all their descendants // children must be String, Text, or Element } class Root extends ParentNode { Root({List children: null, this.host}) { // O(N) in number of children nodes plus all their descendants if (children != null) children = children.map((node) => node is String ? new Text(node) : node).toList(); this._initRoot(children); } external void _initRoot(List children); // appends the given children nodes // children must be Text or Element final Element host; external Element findId(String id); // O(1) // throws if id is null } class ApplicationRoot extends Root { ApplicationRoot ({List children}) : super(children: children); // O(N) in number of children nodes arguments plus all their descendants @override Type getLayoutManager() => rootLayoutManager; // O(1) } Type rootLayoutManager = BlockLayoutManager; // O(1) class SelectorQuery { external SelectorQuery(String selector); // O(F()) where F() is the complexity of the selector external bool matches(Element element); // O(F()) external Element find(Node root); // O(N*F())+O(M) where N is the number of descendants and M the average depth of the tree external List findAll(Node root); // O(N*F())+O(N*M) where N is the number of descendants and M the average depth of the tree // find() and findAll() throw if the root is not one of the following: // - Element // - Fragment // - Root } ```