Sky DOM APIs

SKY MODULE
<!-- part of sky:core -->

<script>
// ELEMENT TREE API

abstract class ChildNode {
  @nonnull external TreeScope get ownerScope; // O(1)

  external ParentNode get parentNode; // O(1)
  external Element get parentElement; // O(1) // if parentNode isn't an element, returns null
  external ChildNode get previousSibling; // O(1)
  external ChildNode get nextSibling; // O(1)

  // the following all throw if parentNode is null
  external void insertBefore(@nonnull List</*@nonnull*/ ChildNode> nodes); // O(N) in number of arguments plus all their descendants
  external void insertAfter(@nonnull List</*@nonnull*/ ChildNode> nodes); // O(N) in number of arguments plus all their descendants
  external void replaceWith(@nonnull List</*@nonnull*/ ChildNode> nodes); // O(N) in number of descendants plus arguments plus all their descendants
  external void remove(); // O(N) in number of descendants

  // called when parentNode changes
  external void parentChangeCallback(ParentNode oldParent, ParentNode newParent, ChildNode previousSibling, ChildNode nextSibling); // 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
}

abstract class Node extends EventTarget {
  @override
  external List</*@nonnull*/ EventTarget> getEventDispatchChain(); // O(N) in number of ancestors across shadow trees
  // implements EventTarget.getEventDispatchChain()
  // returns the event dispatch chain (including handling shadow trees)

  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 reachable from the Application Document, returns null
  // (so in particular orphaned subtrees and nodes in module documents don't have one)
  //  -- should be updated when the node's parent chain changes (same time as, e.g.,
  //     the id hashtable is updated)
  // also always returns null for ContentElement elements and ShadowRoot nodes

  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 ChildNode get firstChild; // O(1)
  external ChildNode get lastChild; // O(1)

  // Returns a new List every time.
  external List</*nonnull*/ ChildNode> getChildNodes(); // O(N) in number of child nodes
  external List<Element> getChildElements(); // O(N) in number of child nodes
  // TODO(ianh): might not be necessary if we have the parser drop unnecessary whitespace text nodes

  external void append(@nonnull List</*nonnull*/ ChildNode> nodes); // O(N) in number of arguments plus all their descendants
  external void prepend(@nonnull List</*nonnull*/ ChildNode> nodes); // O(N) in number of arguments plus all their descendants
  external void replaceChildrenWith(@nonnull List</*nonnull*/ ChildNode> nodes); // O(N) in number of descendants plus arguments plus all their descendants
}

class Attr {
  const Attr (this.name, [this.value = '']); // O(1)
  final String name; // O(1)
  final String value; // O(1)
}

// @tagname annotation for registering elements
// only useful when placed on classes that inherit from Element
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 {
  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
    var tagnameClass = reflectClass(tagname);
    return (reflectClass(this.runtimeType).metadata.singleWhere((mirror) => mirror.type == tagnameClass).reflectee as tagname).value;
  }

  @nonnull external bool hasAttribute(@nonnull String name); // O(N) in number of attributes
  @nonnull external String getAttribute(@nonnull String name); // O(N) in number of attributes
  external void setAttribute(@nonnull String name, [@nonnull String value = '']); // O(N) in number of attributes
  external void removeAttribute(@nonnull String name); // O(N) in number of attributes

  // 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...

  void endTagParsedCallback() { }
  void attributeChangeCallback(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 with ChildNode {
  external Text([@nonnull String value = '']); // O(1)

  @nonnull external String get value; // O(1)
  external void set (@nonnull String value); // O(1)

  void valueChangeCallback(@nonnull String oldValue, @nonnull String newValue) { }

  @override
  Type getLayoutManager() => TextLayoutManager; // O(1)
}

class DocumentFragment extends ParentNode implements FindRoot {
  DocumentFragment([List</*nonnull*/ ChildNode> nodes = null]); // O(N) in number of arguments plus all their descendants
}

abstract class TreeScope extends ParentNode {
  external Document get ownerDocument; // O(1)
  external TreeScope get parentScope; // O(1)

  external Element findId(String id); // O(1)
  // throws if id is null
}

class ShadowRoot extends TreeScope implements FindRoot {
  ShadowRoot([this._host]); // O(1)
  // note that there is no way in the API to use a newly created ShadowRoot currently

  Element _host;
  Element get host => _host; // O(1)
}

class Document extends TreeScope implements FindRoot {
  external Document ([List</*@nonnull*/ ChildNode> nodes = null]); // O(N) in number of arguments plus all their descendants
}

class ApplicationDocument extends Document {
  external ApplicationDocument ([List</*@nonnull*/ ChildNode> nodes = null]); // O(N) in number of /nodes/ arguments plus all their descendants

  @override
  Type getLayoutManager() => rootLayoutManager; // O(1)
}

Type rootLayoutManager = BlockLayoutManager; // O(1)


// BUILT-IN ELEMENTS

@tagname('import')
class ImportElement extends Element {
  ImportElement = Element;

  @override
  Type getLayoutManager() => null; // O(1)
}

@tagname('template')
class TemplateElement extends Element {
  TemplateElement = Element;

  // TODO(ianh): convert <template> to using a token stream instead of a DocumentFragment

  @nonnull external DocumentFragment get content; // O(1)

  @override
  Type getLayoutManager() => null; // O(1)
}

@tagname('script')
class ScriptElement extends Element {
  ScriptElement = Element;

  @override
  Type getLayoutManager() => null; // O(1)
}

@tagname('style')
class StyleElement extends Element {
  StyleElement = Element;

  @nonnull external List</*@nonnull*/ Rule> getRules(); // O(N) in rules

  @override
  Type getLayoutManager() => null; // O(1)
}

@tagname('content')
class ContentElement extends Element {
  ContentElement = Element;

  @nonnull external List</*@nonnull*/ Node> getDistributedNodes(); // O(N) in distributed nodes

  @override
  Type getLayoutManager() => null; // O(1)
}

@tagname('img')
class ImgElement extends Element {
  ImgElement = Element;

  @override
  Type getLayoutManager() => ImgElementLayoutManager; // O(1)
}

@tagname('div')
class DivElement extends Element {
  DivElement = Element;
}

@tagname('span')
class SpanElement extends Element {
  SpanElement = Element;
}

@tagname('iframe')
class IframeElement extends Element {
  IframeElement = Element;

  @override
  Type getLayoutManager() => IframeElementLayoutManager; // O(1)
}

@tagname('t')
class TElement extends Element {
  TElement = Element;
}

@tagname('a')
class AElement extends Element {
  AElement = Element;
}

@tagname('title')
class TitleElement extends Element {
  TitleElement = Element;

  @override
  Type getLayoutManager() => null; // O(1)
}

class _ErrorElement extends Element {
  _ErrorElement._create();

  @override
  Type getLayoutManager() => _ErrorElementLayoutManager; // O(1)
}

class SelectorQuery {
  external SelectorQuery(@nonnull String selector); // O(F()) where F() is the complexity of the selector

  @nonnull external bool matches(@nonnull Element element); // O(F())
  external Element find(@nonnull FindRoot root); // O(N*F())+O(M) where N is the number of descendants and M the average depth of the tree
  @nonnull external List</*@nonnull*/ Element> findAll(FindRoot root); // O(N*F())+O(N*M) where N is the number of descendants and M the average depth of the tree
}
</script>