APIs

The Sky core API

module 'sky:core' {

  // EVENTS

  class Event {
    constructor (String type, Boolean bubbles = true, any data = null); // O(1)
    readonly attribute String type; // O(1)
    readonly attribute Boolean bubbles; // O(1)
    attribute any data; // O(1)

    readonly attribute EventTarget target; // O(1)
    attribute Boolean handled; // O(1)
    attribute any result; // O(1)

    // TODO(ianh): do events get blocked at scope boundaries, e.g. focus events when both sides are in the scope?
    // TODO(ianh): do events get retargetted, e.g. focus when leaving a custom element?
  }

  callback EventListener any (Event event);
    // if the return value is not undefined:
    //   assign it to event.result
    //   set event.handled to true

  abstract class EventTarget {
    any dispatchEvent(Event event); // O(N) in total number of listeners for this type in the chain
      // sets event.handled to false and event.result to undefined
      // makes a record of the event target chain by calling getEventDispatchChain()
      // invokes all the handlers on the chain in turn
      // returns event.result
    virtual Array<EventTarget> getEventDispatchChain(); // O(1) // returns []
    void addEventListener(String type, EventListener listener); // O(1)
    void removeEventListener(String type, EventListener listener); // O(N) in event listeners with that type
    private Array<String> getRegisteredEventListenerTypes(); // O(N)
    private Array<EventListener> getRegisteredEventListenersForType(String type); // O(N)
  }

  class CustomEventTarget : EventTarget { // implemented in JS
    constructor (); // O(1)
    attribute EventTarget parentNode; // getter O(1), setter O(N) in height of tree, throws if this would make a loop

    virtual Array<EventTarget> getEventDispatchChain(); // O(N) in height of tree // implements EventTarget.getEventDispatchChain()
      // let result = [];
      // let node = this;
      // while (node) {
      //   result.push(node);
      //   node = node.parentNode;
      // }
      // return result;

    // you can inherit from this to make your object into an event target
    // or you can inherit from EventTarget and implement your own getEventDispatchChain()
  }



  // DOM

  typedef ChildNode (Element or Text);
  typedef ChildArgument (Element or Text or String);

  abstract class Node : EventTarget { // implemented in C++
    readonly attribute TreeScope? ownerScope; // O(1)
    
    readonly attribute ParentNode? parentNode; // O(1)
    readonly attribute Element? parentElement; // O(1) // if parentNode isn't an element, returns null
    readonly attribute ChildNode? previousSibling; // O(1)
    readonly attribute ChildNode? nextSibling; // O(1)

    virtual Array<EventTarget> getEventDispatchChain(); // O(N) in number of ancestors across shadow trees // implements EventTarget.getEventDispatchChain()
      // returns the event dispatch chain (including handling shadow trees)
    
    // the following all throw if parentNode is null
    void insertBefore(ChildArgument... nodes); // O(N) in number of arguments plus all their descendants
    void insertAfter(ChildArgument... nodes); // O(N) in number of arguments plus all their descendants
    void replaceWith(ChildArgument... nodes); // O(N) in number of descendants plus arguments plus all their descendants
    void remove(); // O(N) in number of descendants
    Node cloneNode(Boolean deep = false); // O(1) if deep=false, O(N) in the number of descendants if deep=true

    // called when parentNode changes
    virtual void parentChangeCallback(ParentNode? oldParent, ParentNode? newParent, ChildNode? previousSibling, ChildNode? nextSibling); // O(N) in descendants (calls attached/detached)
    virtual void attachedCallback(); // noop
    virtual void detachedCallback(); // noop
  }

  abstract class ParentNode : Node {
    readonly attribute ChildNode? firstChild; // O(1)
    readonly attribute ChildNode? lastChild; // O(1)
    
    // Returns a new Array every time.
    Array<ChildNode> getChildNodes(); // O(N) in number of child nodes
    Array<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

    void append(ChildArgument... nodes); // O(N) in number of arguments plus all their descendants
    void prepend(ChildArgument... nodes); // O(N) in number of arguments plus all their descendants
    void replaceChildrenWith(ChildArgument... nodes); // O(N) in number of descendants plus arguments plus all their descendants
  }

  class Attr {
    constructor (String name, String value = ''); // O(1)
    readonly attribute String name; // O(1)
    readonly attribute String value; // O(1)
  }

  abstract class Element : ParentNode {
    readonly attribute String tagName; // O(1)

    Boolean hasAttribute(String name); // O(N) in number of attributes
    String getAttribute(String name); // O(N) in number of attributes
    void setAttribute(String name, String value = ''); // O(N) in number of attributes
    void removeAttribute(String name); // O(N) in number of attributes
    
    // Returns a new Array and new Attr instances every time.
    Array<Attr> getAttributes(); // O(N) in number of attributes

    readonly attribute ShadowRoot? shadowRoot; // O(1) // returns the shadow root
    Array<ContentElement> getDestinationInsertionPoints(); // O(N) in number of insertion points the node is in

    virtual void endTagParsedCallback(); // noop
    virtual void attributeChangeCallback(String name, String? oldValue, String? newValue); // noop
    // TODO(ianh): does a node ever need to know when it's been redistributed?
  }

  class Text : Node {
    constructor (String value = ''); // O(1)
    attribute String value; // O(1)

    void replaceWith(String node); // O(1) // special case override of Node.replaceWith()

    virtual void valueChangeCallback(String? oldValue, String? newValue); // noop
  }

  class DocumentFragment : ParentNode {
    constructor (ChildArguments... nodes); // O(N) in number of arguments plus all their descendants
  }

  abstract class TreeScope : ParentNode {
    readonly attribute Document? ownerDocument; // O(1)
    readonly attribute TreeScope? parentScope; // O(1)

    Element? findId(String id); // O(1)
  }

  class ShadowRoot : TreeScope {
    constructor (Element host); // O(1) // note that there is no way in the API to use a newly created ShadowRoot
    readonly attribute Element host; // O(1)
  }

  class Document : TreeScope {
    constructor (ChildArguments... nodes); // O(N) in number of arguments plus all their descendants
  }
  
  class SelectorQuery {
    constructor (String selector); // O(F()) where F() is the complexity of the selector

    Boolean matches(Element element); // O(F())
    Element? find(Element root); // O(N*F())+O(M) where N is the number of descendants and M the average depth of the tree
    Element? find(DocumentFragment root); // O(N*F())+O(M) where N is the number of descendants and M the average depth of the tree
    Element? find(TreeScope root); // O(N*F()) where N is the number of descendants
    Array<Element> findAll(Element root); // O(N*F())+O(N*M) where N is the number of descendants and M the average depth of the tree
    Array<Element> findAll(DocumentFragment root); // O(N*F())+O(N*M) where N is the number of descendants and M the average depth of the tree
    Array<Element> findAll(TreeScope root); // O(N*F()) where N is the number of descendants
  }


  // BUILT-IN ELEMENTS

  class ImportElement : Element {
    constructor (Dictionary<String> attributes, ChildArguments... nodes); // O(M+N), M = number of attributes, N = number of nodes plus all their descendants
    constructor (ChildArguments... nodes); // shorthand
    constructor (Dictionary<String> attributes); // shorthand
    constructor (); // shorthand
    constructor attribute String tagName; // O(1) // "import"
    constructor attribute Boolean shadow; // O(1) // false
  }
  class TemplateElement : Element {
    constructor (Dictionary<String> attributes, ChildArguments... nodes); // O(M+N), M = number of attributes, N = number of nodes plus all their descendants
    constructor (ChildArguments... nodes); // shorthand
    constructor (Dictionary<String> attributes); // shorthand
    constructor (); // shorthand
    constructor attribute String tagName; // O(1) // "template"
    constructor attribute Boolean shadow; // O(1) // false

    readonly attribute DocumentFragment content; // O(1)
  }
  class ScriptElement : Element {
    constructor (Dictionary<String> attributes, ChildArguments... nodes); // O(M+N), M = number of attributes, N = number of nodes plus all their descendants
    constructor (ChildArguments... nodes); // shorthand
    constructor (Dictionary<String> attributes); // shorthand
    constructor (); // shorthand
    constructor attribute String tagName; // O(1) // "script"
    constructor attribute Boolean shadow; // O(1) // false
  }
  class StyleElement : Element {
    constructor (Dictionary<String> attributes, ChildArguments... nodes); // O(M+N), M = number of attributes, N = number of nodes plus all their descendants
    constructor (ChildArguments... nodes); // shorthand
    constructor (Dictionary<String> attributes); // shorthand
    constructor (); // shorthand
    constructor attribute String tagName; // O(1) // "style"
    constructor attribute Boolean shadow; // O(1) // false
  }
  class ContentElement : Element {
    constructor (Dictionary<String> attributes, ChildArguments... nodes); // O(M+N), M = number of attributes, N = number of nodes plus all their descendants
    constructor (ChildArguments... nodes); // shorthand
    constructor (Dictionary<String> attributes); // shorthand
    constructor (); // shorthand
    constructor attribute String tagName; // O(1) // "content"
    constructor attribute Boolean shadow; // O(1) // false

    Array<Node> getDistributedNodes(); // O(N) in distributed nodes
  }
  class ImgElement : Element {
    constructor (Dictionary<String> attributes, ChildArguments... nodes); // O(M+N), M = number of attributes, N = number of nodes plus all their descendants
    constructor (ChildArguments... nodes); // shorthand
    constructor (Dictionary<String> attributes); // shorthand
    constructor (); // shorthand
    constructor attribute String tagName; // O(1) // "img"
    constructor attribute Boolean shadow; // O(1) // false
  }
  class DivElement : Element {
    constructor (Dictionary<String> attributes, ChildArguments... nodes); // O(M+N), M = number of attributes, N = number of nodes plus all their descendants
    constructor (ChildArguments... nodes); // shorthand
    constructor (Dictionary<String> attributes); // shorthand
    constructor (); // shorthand
    constructor attribute String tagName; // O(1) // "div"
    constructor attribute Boolean shadow; // O(1) // false
  }
  class SpanElement : Element {
    constructor (Dictionary<String> attributes, ChildArguments... nodes); // O(M+N), M = number of attributes, N = number of nodes plus all their descendants
    constructor (ChildArguments... nodes); // shorthand
    constructor (Dictionary<String> attributes); // shorthand
    constructor (); // shorthand
    constructor attribute String tagName; // O(1) // "span"
    constructor attribute Boolean shadow; // O(1) // false
  }
  class IframeElement : Element {
    constructor (Dictionary<String> attributes, ChildArguments... nodes); // O(M+N), M = number of attributes, N = number of nodes plus all their descendants
    constructor (ChildArguments... nodes); // shorthand
    constructor (Dictionary<String> attributes); // shorthand
    constructor (); // shorthand
    constructor attribute String tagName; // O(1) // "iframe"
    constructor attribute Boolean shadow; // O(1) // false
  }
  class TElement : Element {
    constructor (Dictionary<String> attributes, ChildArguments... nodes); // O(M+N), M = number of attributes, N = number of nodes plus all their descendants
    constructor (ChildArguments... nodes); // shorthand
    constructor (Dictionary<String> attributes); // shorthand
    constructor (); // shorthand
    constructor attribute String tagName; // O(1) // "t"
    constructor attribute Boolean shadow; // O(1) // false
  }
  class AElement : Element {
    constructor (Dictionary<String> attributes, ChildArguments... nodes); // O(M+N), M = number of attributes, N = number of nodes plus all their descendants
    constructor (ChildArguments... nodes); // shorthand
    constructor (Dictionary<String> attributes); // shorthand
    constructor (); // shorthand
    constructor attribute String tagName; // O(1) // "a"
    constructor attribute Boolean shadow; // O(1) // false
  }
  class TitleElement : Element {
    constructor (Dictionary<String> attributes, ChildArguments... nodes); // O(M+N), M = number of attributes, N = number of nodes plus all their descendants
    constructor (ChildArguments... nodes); // shorthand
    constructor (Dictionary<String> attributes); // shorthand
    constructor (); // shorthand
    constructor attribute String tagName; // O(1) // "title"
    constructor attribute Boolean shadow; // O(1) // false
  }
  class ErrorElement : Element {
    constructor (Dictionary<String> attributes, ChildArguments... nodes); // O(M+N), M = number of attributes, N = number of nodes plus all their descendants
    constructor (ChildArguments... nodes); // shorthand
    constructor (Dictionary<String> attributes); // shorthand
    constructor (); // shorthand
    constructor attribute String tagName; // O(1) // "error"
    constructor attribute Boolean shadow; // O(1) // false
  }



  // MODULES

  callback InternalElementConstructor void (Module module);
  dictionary ElementRegistration {
    String tagName;
    Boolean shadow = false;
    InternalElementConstructor? constructor = null;
  }

  interface ElementConstructor {
    constructor (Dictionary<String> attributes, ChildArguments... nodes); // O(M+N), M = number of attributes, N = number of nodes plus all their descendants
    constructor (ChildArguments... nodes); // shorthand
    constructor (Dictionary<String> attributes); // shorthand
    constructor (); // shorthand

    constructor attribute String tagName;
    constructor attribute Boolean shadow;
  }

  abstract class AbstractModule : EventTarget {
    readonly attribute Document document; // O(1) // the Documentof 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(ElementRegistration 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:
    //  1. let constructor be the constructor passed in, if any
    //  2. let prototype be the constructor's prototype; if there is no
    //     constructor, let prototype be Element
    //  3. create a new Function that:
    //      1. throws if not called as a constructor
    //      2. creates an actual Element object
    //      3. initialises the shadow tree if shadow on the options is true
    //      4. calls constructor, if it's not null, with the module as the argument
    //  4. let that new Function's prototype be the aforementioned prototype
    //  5. let that new Function have tagName and shadow properties set to
    //     the values passed in on options
    //  6. register the new element

    readonly attribute ScriptElement? currentScript; // O(1) // returns the <script> element currently being executed if any, and if it's in this module; else null
  }

  class Module : AbstractModule {
    constructor (Application application, Document document, String url); // O(1)
    readonly attribute Application application; // O(1)

    attribute any exports; // O(1) // defaults to {}
  }

  class Application : AbstractModule {
    constructor (Document document, String url); // O(1)
    attribute String title; // O(1)
  }

  // see script.md for a description of the global object, though note that
  // the sky core module doesn't use it or affect it in any way.

}

TODO(ianh): event loop

TODO(ianh): define the DOM APIs listed above, including firing the change callbacks

TODO(ianh): schedule microtask, schedule task, requestAnimationFrame, custom element callbacks...

Appendices

Sky IDL

The Sky IDL language is used to describe JS APIs found in Sky, in particular, the JS APIs exposed by the four magical imports defined in this document.

Sky IDL definitions are typically compiled to C++ that exposes the C++ implementations of the APIs to JavaScript.

Sky IDL works more or less the same as Web IDL but the syntax is a bit different.

module 'sky:modulename' {

  // this is a comment

  typedef NewType OldType; // useful when OldType is a commonly-used union

  callback CallbackName ReturnType (ArgumentType argumentName);

  class ClassName {
    // a class corresponds to a JavaScript prototype
    // corresponds to a WebIDL 'interface'
  }

  abstract class Superclass {
    // an abstract class can't have a constructor
    // in every other respect it is the same as a regular class
  }

  class Subclass : Superclass {
    // properties
    readonly attribute ReturnType attributeName; // getter
    attribute ReturnType attributeName; // getter and setter

    // methods and constructors
    constructor ();
    ReturnType method();
      // When the platform calls this method, it always invokes the "real" method, even if it's been
      // deleted from the prototypes (as if it took a reference to the method at startup, and stored
      // state using Symbols)
      // Calling a method with fewer arguments than defined will throw.
      // Calling a method with more arguments ignores the extra arguments.
    virtual ReturnType methodCallback();
      // when the platform calls this, it actually calls it the way JS would, so author overrides do
      // affect what gets called. Make sure if you override it that you call the superclass implementation!
      // The default implementations of 'virtual' methods all end by calling the identically named method
      // on the superclass, if there is such a method.

    // properties on the constructor
    constructor readonly attribute ReturnType staticName;

    // private APIs - see below
    private void method();

    // arguments and overloading are done as follows
    // note that the argument names are only for documentation purposes
    ReturnType method(ArgumentType argumentName1, ArgumentType argumentName2);
    // the last argument's type can have "..." appended to it to indicate a varargs-like situation
    ReturnType method(ArgumentType argumentName1, ArgumentType... allSubsequentArguments);
    // trailing arguments can have a default value, which must be a literal of the given type
    ReturnType method(ArgumentType argumentName1, ArgumentType argumentName2 = defaultValue);
  }

  dictionary Options {
    String foo; // if there's no default, the property must be specified or it's a TypeError
    Integer bar = 4; // properties can have default values
  }

  // the module can have properties and methods also
  attribute String Foo;
  void method();

  interface InterfaceName {
    // describes a template of a prototype, in the same syntax as a class
    // not actually exposed in the runtime
  }

}

Private APIs

Private APIs are only accessible via Symbol objects, which are then exposed on the sky:debug module's exports object as the name of the member given in the IDL.

For example, consider:

class Foo {
  private void Bar();
}

In a script with a foo object of type Foo, foo.Bar is undefined. However, it can be obtained as follows:

<import src="sky:debug" as="debug"/>
<!-- ... import whatever defines 'foo' ... -->
<script>
  foo[debug.Bar]
</script>

Types

The following types are available:

  • Integer - WebIDL long long
  • Float - WebIDL double
  • Infinity - singleton type with value Infinity
  • String - WebIDL USVString
  • Boolean - WebIDL boolean

Object - WebIDL object (ClassName can be used as a literal for this type)

  • ClassName - an instance of the class ClassName
  • DictionaryName - an instance of the dictionary DictionaryName
  • Promise<Type> - WebIDL Promise<T>
  • Generator<Type> - An ECMAScript generator function that returns data of the given type
  • Array<Type> - WebIDL sequence<T>
  • Dictionary<Type> - unordered set of name-value String-Type pairs with no duplicate names
  • Type? - union of Type and the singleton type with value null (WebIDL nullable)
  • (Type1 or Type2) - union of Type1 and Type2 (WebIDL union)
  • any - union of all types (WebIDL any)

Methods that return nothing (undefined, in JS) use the keyword “void” instead of a type.

TODO(ianh): Define in detail how this actually works

Mojom IDL

The Mojom IDL language is used to describe the APIs exposed over Mojo pipes.

Mojom IDL definitions are typically compiled to wrappers in each language, which are then used as imports.

TODO(ianh): Define in detail how this actually works

Notes

global object = {} // with Math, RegExp, etc

magical imports:
  the core mojo fabric JS API   sky:mojo:fabric:core
  the asyncWait/cancelWait mojo fabric JS API (interface to IPC thread)  sky:mojo:fabric:ipc
  the mojom for the shell, proxying through C++ so that the shell pipe isn't exposed  sky:mojo:shell
  the sky API  sky:core
  the sky debug symbols for private APIs  sky:debug

TODO(ianh): determine if we want to separate the “this” from the Document, especially for Modules, so that exposing a module‘s element doesn’t expose the module's exports attribute.