| Sky DOM APIs |
| ============ |
| |
| ```dart |
| // ELEMENT TREE API |
| |
| abstract class Node extends EventTarget { |
| @override |
| external List<EventTarget> 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<ContentElement> getDestinationInsertionPoints(); // O(N) in number of insertion points the node is in |
| // returns the <content> 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<Node> getChildren(); // O(N) in number of child nodes |
| List<Element> 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<String, String> 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<String, String> 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<Attr> 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<Element> 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 |
| } |
| ``` |