about summary refs log tree commit diff stats
path: root/src/html/dom.nim
diff options
context:
space:
mode:
Diffstat (limited to 'src/html/dom.nim')
-rw-r--r--src/html/dom.nim299
1 files changed, 120 insertions, 179 deletions
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 950c2903..e628da6e 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -109,7 +109,6 @@ type
     timeouts*: TimeoutState
     navigate*: proc(url: URL)
     importMapsAllowed*: bool
-    factory*: CAtomFactory
     pendingResources*: seq[EmptyPromise]
     imageURLCache: Table[string, CachedURLImage]
     svgCache*: Table[string, SVGSVGElement]
@@ -150,11 +149,13 @@ type
     element: Element
     attrlist: seq[Attr]
 
+  CollectionMatchFun = proc(node: Node): bool {.noSideEffect.}
+
   Collection = ref object of RootObj
     islive: bool
     childonly: bool
     root: Node
-    match: proc(node: Node): bool {.noSideEffect.}
+    match: CollectionMatchFun
     snapshot: seq[Node]
     livelen: int
 
@@ -200,7 +201,6 @@ type
     i*: int
 
   Document* = ref object of Node
-    factory*: CAtomFactory
     charset*: Charset
     window* {.jsget: "defaultView".}: Window
     url* {.jsget: "URL".}: URL # not nil
@@ -1100,41 +1100,8 @@ func document*(node: Node): Document =
 template document*(element: Element): Document =
   element.internalDocument
 
-proc toAtom*(window: Window; atom: StaticAtom): CAtom =
-  return window.factory.toAtom(atom)
-
-proc toAtom*(window: Window; s: sink string): CAtom =
-  return window.factory.toAtom(s)
-
-proc toStr*(window: Window; atom: CAtom): lent string =
-  return window.factory.toStr(atom)
-
-proc toAtom*(document: Document; s: sink string): CAtom =
-  return document.factory.toAtom(s)
-
-proc toAtomLower*(document: Document; s: sink string): CAtom =
-  return document.factory.toAtomLower(s)
-
-proc toAtom*(document: Document; at: StaticAtom): CAtom =
-  return document.factory.toAtom(at)
-
-proc toStr(document: Document; atom: CAtom): lent string =
-  return document.factory.toStr(atom)
-
-proc toStaticAtom(document: Document; atom: CAtom): StaticAtom =
-  return document.factory.toStaticAtom(atom)
-
-proc toAtom*(document: Document; tagType: TagType): CAtom =
-  return document.factory.toAtom(tagType)
-
-proc toAtom(document: Document; namespace: Namespace): CAtom =
-  return document.factory.toAtom(namespace)
-
-proc toAtom(document: Document; prefix: NamespacePrefix): CAtom =
-  return document.factory.toAtom(prefix)
-
 func namespace*(element: Element): Namespace =
-  return element.document.factory.toNamespace(element.namespaceURI)
+  return element.namespaceURI.toNamespace()
 
 func tagTypeNoNS(element: Element): TagType =
   return element.localName.toTagType()
@@ -1151,7 +1118,7 @@ func findAttr(element: Element; qualifiedName: CAtom): int =
   return -1
 
 func findAttr(element: Element; qualifiedName: StaticAtom): int =
-  return element.findAttr(element.document.toAtom(qualifiedName))
+  return element.findAttr(qualifiedName.toAtom())
 
 func findAttrNS(element: Element; namespace, qualifiedName: CAtom): int =
   for i, attr in element.attrs.mypairs:
@@ -1186,7 +1153,7 @@ func escapeText(s: string; attributeMode = false): string =
 
 when defined(debug):
   func localNameStr*(element: Element): string =
-    return element.document.toStr(element.localName)
+    return element.localName.toStr()
 
   func `$`*(node: Node): string =
     if node == nil:
@@ -1195,7 +1162,7 @@ when defined(debug):
       let element = Element(node)
       result = "<" & element.localNameStr
       for attr in element.attrs:
-        let k = element.document.toStr(attr.localName)
+        let k = attr.localName.toStr()
         result &= ' ' & k & "=\"" & attr.value.escapeText(true) & "\""
       result &= ">\n"
       for node in element.childList:
@@ -1308,9 +1275,8 @@ iterator inputs(form: HTMLFormElement): HTMLInputElement {.inline.} =
       yield HTMLInputElement(control)
 
 iterator radiogroup*(input: HTMLInputElement): HTMLInputElement {.inline.} =
-  let empty = input.document.toAtom("")
   let name = input.name
-  if name != CAtomNull and name != empty:
+  if name != CAtomNull and name != satUempty.toAtom():
     if input.form != nil:
       for input in input.form.inputs:
         if input.name == name and input.inputType == itRadio:
@@ -1422,8 +1388,6 @@ proc findNode(collection: Collection; node: Node): int =
   collection.refreshCollection()
   return collection.snapshot.find(node)
 
-type CollectionMatchFun = proc(node: Node): bool {.noSideEffect.}
-
 func newCollection[T: Collection](root: Node; match: CollectionMatchFun;
     islive, childonly: bool): T =
   result = T(
@@ -1529,10 +1493,7 @@ proc setCookie(document: Document; cookie: string) {.jsfset: "cookie".} =
 
 # DOMTokenList
 proc newDOMTokenList(element: Element; name: StaticAtom): DOMTokenList =
-  return DOMTokenList(
-    element: element,
-    localName: element.document.toAtom(name)
-  )
+  return DOMTokenList(element: element, localName: name.toAtom())
 
 iterator items*(tokenList: DOMTokenList): CAtom {.inline.} =
   for tok in tokenList.toks:
@@ -1553,19 +1514,18 @@ func contains(tokenList: DOMTokenList; a: CAtom): bool =
   return a in tokenList.toks
 
 func containsIgnoreCase(tokenList: DOMTokenList; a: StaticAtom): bool =
-  let document = tokenList.element.document
-  return document.factory.containsIgnoreCase(tokenList.toks, a)
+  return tokenList.toks.containsIgnoreCase(a)
 
-func jsContains(tokenList: DOMTokenList; s: string): bool
+proc jsContains(tokenList: DOMTokenList; s: string): bool
     {.jsfunc: "contains".} =
-  return tokenList.element.document.toAtom(s) in tokenList.toks
+  return s.toAtom() in tokenList.toks
 
 func `$`(tokenList: DOMTokenList): string {.jsfunc: "toString".} =
   var s = ""
   for i, tok in tokenList.toks:
     if i != 0:
       s &= ' '
-    s &= tokenList.element.document.toStr(tok)
+    s &= tok.toStr()
   return move(s)
 
 proc update(tokenList: DOMTokenList) =
@@ -1574,7 +1534,7 @@ proc update(tokenList: DOMTokenList) =
     return
   tokenList.element.attr(tokenList.localName, $tokenList)
 
-func validateDOMToken(ctx: JSContext; document: Document; tok: JSValue):
+proc validateDOMToken(ctx: JSContext; document: Document; tok: JSValue):
     DOMResult[CAtom] =
   var res: string
   ?ctx.fromJS(tok, res)
@@ -1583,7 +1543,7 @@ func validateDOMToken(ctx: JSContext; document: Document; tok: JSValue):
   if AsciiWhitespace in res:
     return errDOMException("Got a string containing whitespace",
       "InvalidCharacterError")
-  return ok(document.toAtom(res))
+  return ok(res.toAtom())
 
 proc add(ctx: JSContext; tokenList: DOMTokenList; tokens: varargs[JSValue]):
     Err[DOMException] {.jsfunc.} =
@@ -1643,7 +1603,7 @@ const SupportedTokensMap = {
 
 func supports(tokenList: DOMTokenList; token: string):
     JSResult[bool] {.jsfunc.} =
-  let localName = tokenList.element.document.toStaticAtom(tokenList.localName)
+  let localName = tokenList.localName.toStaticAtom()
   for it in SupportedTokensMap:
     if it[0] == localName:
       let lowercase = token.toLowerAscii()
@@ -1673,15 +1633,15 @@ proc validateQName(qname: string): DOMResult[void] =
 
 # DOMStringMap
 proc delete(map: var DOMStringMap; name: string): bool {.jsfunc.} =
-  let name = map.target.document.toAtom("data-" & name.camelToKebabCase())
+  let name = ("data-" & name.camelToKebabCase()).toAtom()
   let i = map.target.findAttr(name)
   if i != -1:
     map.target.delAttr(i)
   return i != -1
 
-func getter(ctx: JSContext; map: var DOMStringMap; name: string): JSValue
+proc getter(ctx: JSContext; map: var DOMStringMap; name: string): JSValue
     {.jsgetownprop.} =
-  let name = map.target.document.toAtom("data-" & name.camelToKebabCase())
+  let name = ("data-" & name.camelToKebabCase()).toAtom()
   let i = map.target.findAttr(name)
   if i != -1:
     return ctx.toJS(map.target.attrs[i].value)
@@ -1698,7 +1658,7 @@ proc setter(map: var DOMStringMap; name, value: string): Err[DOMException]
       "InvalidCharacterError")
   let name = "data-" & name.camelToKebabCase()
   ?name.validateName()
-  let aname = map.target.document.toAtom(name)
+  let aname = name.toAtom()
   map.target.attr(aname, value)
   return ok()
 
@@ -1706,7 +1666,7 @@ func names(ctx: JSContext; map: var DOMStringMap): JSPropertyEnumList
     {.jspropnames.} =
   var list = newJSPropertyEnumList(ctx, uint32(map.target.attrs.len))
   for attr in map.target.attrs:
-    let k = map.target.document.toStr(attr.localName)
+    let k = attr.localName.toStr()
     if k.startsWith("data-") and AsciiUpperAlpha notin k:
       list.add(k["data-".len .. ^1].kebabToCamelCase())
   return list
@@ -1767,16 +1727,15 @@ proc names(ctx: JSContext; collection: HTMLCollection): JSPropertyEnumList
   let L = collection.length
   var list = newJSPropertyEnumList(ctx, L)
   var ids = initOrderedSet[CAtom]()
-  let empty = ctx.toAtom("")
   for u in 0 ..< L:
     list.add(u)
     let element = collection.item(u)
-    if element.id != CAtomNull and element.id != empty:
+    if element.id != CAtomNull and element.id != satUempty.toAtom():
       ids.incl(element.id)
     if element.namespace == Namespace.HTML:
       ids.incl(element.name)
   for id in ids:
-    list.add(collection.root.document.toStr(id))
+    list.add(id.toStr())
   return list
 
 # HTMLFormControlsCollection
@@ -2035,10 +1994,10 @@ proc getAttr(map: NamedNodeMap; dataIdx: int): Attr =
   map.attrlist.add(attr)
   return attr
 
-func normalizeAttrQName(element: Element; qualifiedName: string): CAtom =
+proc normalizeAttrQName(element: Element; qualifiedName: string): CAtom =
   if element.namespace == Namespace.HTML and not element.document.isxml:
-    return element.document.toAtomLower(qualifiedName)
-  return element.document.toAtom(qualifiedName)
+    return qualifiedName.toAtomLower()
+  return qualifiedName.toAtom()
 
 func hasAttributes(element: Element): bool {.jsfunc.} =
   return element.attrs.len > 0
@@ -2056,22 +2015,22 @@ func attributes(element: Element): NamedNodeMap {.jsfget.} =
     ))
   return element.cachedAttributes
 
-func findAttr(element: Element; qualifiedName: string): int =
+proc findAttr(element: Element; qualifiedName: string): int =
   return element.findAttr(element.normalizeAttrQName(qualifiedName))
 
-func findAttrNS(element: Element; namespace, localName: string): int =
-  let namespace = element.document.toAtom(namespace)
-  let localName = element.document.toAtom(localName)
+proc findAttrNS(element: Element; namespace, localName: string): int =
+  let namespace = namespace.toAtom()
+  let localName = localName.toAtom()
   return element.findAttrNS(namespace, localName)
 
-func hasAttribute(element: Element; qualifiedName: string): bool {.jsfunc.} =
+proc hasAttribute(element: Element; qualifiedName: string): bool {.jsfunc.} =
   return element.findAttr(qualifiedName) != -1
 
-func hasAttributeNS(element: Element; namespace, localName: string): bool
+proc hasAttributeNS(element: Element; namespace, localName: string): bool
     {.jsfunc.} =
   return element.findAttrNS(namespace, localName) != -1
 
-func getAttribute(ctx: JSContext; element: Element; qualifiedName: string):
+func getAttribute(ctx: JSContext; element: Element; qualifiedName: CAtom):
     JSValue {.jsfunc.} =
   let i = element.findAttr(qualifiedName)
   if i != -1:
@@ -2079,7 +2038,7 @@ func getAttribute(ctx: JSContext; element: Element; qualifiedName: string):
   return JS_NULL
 
 func getAttributeNS(ctx: JSContext; element: Element;
-    namespace, localName: string): JSValue {.jsfunc.} =
+    namespace, localName: CAtom): JSValue {.jsfunc.} =
   let i = element.findAttrNS(namespace, localName)
   if i != -1:
     return ctx.toJS(element.attrs[i].value)
@@ -2107,7 +2066,7 @@ proc item(map: NamedNodeMap; i: uint32): Attr {.jsfunc.} =
     return map.getAttr(int(i))
   return nil
 
-func getter(ctx: JSContext; map: NamedNodeMap; atom: JSAtom): Opt[Attr]
+proc getter(ctx: JSContext; map: NamedNodeMap; atom: JSAtom): Opt[Attr]
     {.jsgetownprop.} =
   var u: uint32
   if ctx.fromJS(atom, u).isSome:
@@ -2128,7 +2087,7 @@ func names(ctx: JSContext; map: NamedNodeMap): JSPropertyEnumList
   var names: HashSet[string]
   let element = map.element
   for attr in element.attrs:
-    let name = element.document.toStr(attr.qualifiedName)
+    let name = attr.qualifiedName.toStr()
     if element.namespace == Namespace.HTML and AsciiUpperAlpha in name:
       continue
     if name in names:
@@ -2141,11 +2100,10 @@ func length(characterData: CharacterData): uint32 {.jsfget.} =
   return uint32(characterData.data.utf16Len)
 
 func tagName(element: Element): string {.jsfget.} =
-  let document = element.document
-  result = document.toStr(element.prefix)
+  result = element.prefix.toStr()
   if result.len > 0:
     result &= ':'
-  result &= document.toStr(element.localName)
+  result &= element.localName.toStr()
   if element.namespace == Namespace.HTML:
     result = result.toUpperAscii()
 
@@ -2154,7 +2112,7 @@ func nodeName(node: Node): string {.jsfget.} =
     return Element(node).tagName
   if node of Attr:
     let attr = Attr(node)
-    return attr.ownerElement.document.toStr(attr.data.qualifiedName)
+    return attr.data.qualifiedName.toStr()
   if node of DocumentType:
     return DocumentType(node).name
   if node of CDATASection:
@@ -2417,17 +2375,17 @@ func findFirstChildNotOf(node: Node; tagType: set[TagType]): Element =
       return element
   return nil
 
-func getElementById(document: Document; id: string): Element {.jsfunc.} =
+proc getElementById(document: Document; id: string): Element {.jsfunc.} =
   if id.len == 0:
     return nil
-  let id = document.toAtom(id)
+  let id = id.toAtom()
   for child in document.elements:
     if child.id == id:
       return child
   return nil
 
-func getElementsByName(document: Document; name: CAtom): NodeList {.jsfunc.} =
-  if name == document.toAtom(""):
+proc getElementsByName(document: Document; name: CAtom): NodeList {.jsfunc.} =
+  if name == satUempty.toAtom():
     return document.newNodeList(
       func(node: Node): bool =
         return false,
@@ -2441,11 +2399,11 @@ func getElementsByName(document: Document; name: CAtom): NodeList {.jsfunc.} =
     childonly = false
   )
 
-func getElementsByTagNameImpl(root: Node; tagName: string): HTMLCollection =
+proc getElementsByTagNameImpl(root: Node; tagName: string): HTMLCollection =
   if tagName == "*":
     return root.newHTMLCollection(isElement, islive = true, childonly = false)
-  let localName = root.document.toAtom(tagName)
-  let localNameLower = root.document.factory.toLowerAscii(localName)
+  let localName = tagName.toAtom()
+  let localNameLower = localName.toLowerAscii()
   return root.newHTMLCollection(
     func(node: Node): bool =
       if node of Element:
@@ -2458,27 +2416,26 @@ func getElementsByTagNameImpl(root: Node; tagName: string): HTMLCollection =
     childonly = false
   )
 
-func getElementsByTagName(document: Document; tagName: string): HTMLCollection
+proc getElementsByTagName(document: Document; tagName: string): HTMLCollection
     {.jsfunc.} =
   return document.getElementsByTagNameImpl(tagName)
 
-func getElementsByTagName(element: Element; tagName: string): HTMLCollection
+proc getElementsByTagName(element: Element; tagName: string): HTMLCollection
     {.jsfunc.} =
   return element.getElementsByTagNameImpl(tagName)
 
-func getElementsByClassNameImpl(node: Node; classNames: string):
+proc getElementsByClassNameImpl(node: Node; classNames: string):
     HTMLCollection =
   var classAtoms = newSeq[CAtom]()
   for class in classNames.split(AsciiWhitespace):
-    classAtoms.add(node.document.toAtom(class))
+    classAtoms.add(class.toAtom())
   return node.newHTMLCollection(
     func(node: Node): bool =
       if node of Element:
         let element = Element(node)
         if element.document.mode == QUIRKS:
-          let factory = element.document.factory
           for class in classAtoms:
-            if not factory.containsIgnoreCase(element.classList.toks, class):
+            if not element.classList.toks.containsIgnoreCase(class):
               return false
         else:
           for class in classAtoms:
@@ -2489,11 +2446,11 @@ func getElementsByClassNameImpl(node: Node; classNames: string):
     childonly = false
   )
 
-func getElementsByClassName(document: Document; classNames: string):
+proc getElementsByClassName(document: Document; classNames: string):
     HTMLCollection {.jsfunc.} =
   return document.getElementsByClassNameImpl(classNames)
 
-func getElementsByClassName(element: Element; classNames: string):
+proc getElementsByClassName(element: Element; classNames: string):
     HTMLCollection {.jsfunc.} =
   return element.getElementsByClassNameImpl(classNames)
 
@@ -2524,25 +2481,23 @@ proc names(ctx: JSContext; document: Document): JSPropertyEnumList
   #TODO I'm not quite sure why location isn't added, so I'll add it
   # manually for now.
   list.add("location")
-  let empty = ctx.toAtom("")
   #TODO exposed embed, exposed object
   for child in document.elements({TAG_FORM, TAG_IFRAME, TAG_IMG}):
-    if child.name != CAtomNull and child.name != empty:
+    if child.name != CAtomNull and child.name != satUempty.toAtom():
       if child.tagType == TAG_IMG and child.id != CAtomNull and
-          child.id != empty:
-        list.add(ctx.toStr(child.id))
-      list.add(ctx.toStr(child.name))
+          child.id != satUempty.toAtom():
+        list.add(child.id.toStr())
+      list.add(child.name.toStr())
   return list
 
 proc getter(ctx: JSContext; document: Document; s: string): JSValue
     {.jsgetownprop.} =
   if s.len != 0:
-    let id = ctx.toAtom(s)
-    let empty = ctx.toAtom("")
+    let id = s.toAtom()
     #TODO exposed embed, exposed object
     for child in document.elements({TAG_FORM, TAG_IFRAME, TAG_IMG}):
       if child.tagType == TAG_IMG and child.id == id and
-          child.name != CAtomNull and child.name != empty:
+          child.name != CAtomNull and child.name != satUempty.toAtom():
         return ctx.toJS(child)
       if child.name == id:
         return ctx.toJS(child)
@@ -2558,7 +2513,7 @@ func attr*(element: Element; s: CAtom): lent string =
     return emptyStr
 
 func attr*(element: Element; s: StaticAtom): lent string =
-  return element.attr(element.document.toAtom(s))
+  return element.attr(s.toAtom())
 
 func attrl*(element: Element; s: StaticAtom): Option[int32] =
   return parseInt32(element.attr(s))
@@ -2579,8 +2534,7 @@ func attrb*(element: Element; s: CAtom): bool =
   return element.findAttr(s) != -1
 
 func attrb*(element: Element; at: StaticAtom): bool =
-  let atom = element.document.toAtom(at)
-  return element.attrb(atom)
+  return element.attrb(at.toAtom())
 
 # https://html.spec.whatwg.org/multipage/parsing.html#serialising-html-fragments
 func serializesAsVoid(element: Element): bool =
@@ -2590,13 +2544,13 @@ func serializesAsVoid(element: Element): bool =
 func serializeFragmentInner(res: var string; child: Node; parentType: TagType) =
   if child of Element:
     let element = Element(child)
-    let tags = element.document.toStr(element.localName)
+    let tags = element.localName.toStr()
     res &= '<'
     #TODO qualified name if not HTML, SVG or MathML
     res &= tags
     #TODO custom elements
     for attr in element.attrs:
-      let k = element.document.toStr(attr.qualifiedName)
+      let k = attr.qualifiedName.toStr()
       res &= ' ' & k & "=\"" & attr.value.escapeText(true) & "\""
     res &= '>'
     res.serializeFragment(element)
@@ -2692,8 +2646,8 @@ func applyMediaQuery(ss: CSSStylesheet; window: Window): CSSStylesheet =
 
 proc applyUASheet*(document: Document) =
   const ua = staticRead"res/ua.css"
-  document.uaSheets.add(ua.parseStylesheet(document.factory, nil,
-    document.window.attrsp).applyMediaQuery(document.window))
+  document.uaSheets.add(ua.parseStylesheet(nil, document.window.attrsp)
+    .applyMediaQuery(document.window))
   if document.documentElement != nil:
     document.documentElement.invalidate()
 
@@ -2701,14 +2655,14 @@ proc applyQuirksSheet*(document: Document) =
   if document.window == nil:
     return
   const quirks = staticRead"res/quirk.css"
-  document.uaSheets.add(quirks.parseStylesheet(document.factory, nil,
-    document.window.attrsp).applyMediaQuery(document.window))
+  document.uaSheets.add(quirks.parseStylesheet(nil, document.window.attrsp)
+    .applyMediaQuery(document.window))
   if document.documentElement != nil:
     document.documentElement.invalidate()
 
 proc applyUserSheet*(document: Document; user: string) =
-  document.userSheet = user.parseStylesheet(document.factory, nil,
-    document.window.attrsp).applyMediaQuery(document.window)
+  document.userSheet = user.parseStylesheet(nil, document.window.attrsp)
+    .applyMediaQuery(document.window)
   if document.documentElement != nil:
     document.documentElement.invalidate()
 
@@ -2785,10 +2739,10 @@ func formmethod*(element: Element): FormMethod =
         return parseFormMethod(element.form.attr(satMethod))
   return fmGet
 
-func findAnchor*(document: Document; id: string): Element =
+proc findAnchor*(document: Document; id: string): Element =
   if id.len == 0:
     return nil
-  let id = document.toAtom(id)
+  let id = id.toAtom()
   for child in document.elements:
     if child.id == id:
       return child
@@ -2856,7 +2810,7 @@ proc fireEvent*(window: Window; event: Event; target: EventTarget) =
   discard window.jsctx.dispatch(target, event)
 
 proc fireEvent*(window: Window; name: StaticAtom; target: EventTarget) =
-  let event = newEvent(window.toAtom(name), target)
+  let event = newEvent(name.toAtom(), target)
   event.isTrusted = true
   window.fireEvent(event, target)
 
@@ -2892,8 +2846,7 @@ proc hyperlinkGet(ctx: JSContext; this: JSValue; magic: cint): JSValue
   let url = element.reinitURL()
   if url.isSome:
     let href = ctx.toJS(url.get)
-    let s = ctx.toStr(sa)
-    let res = JS_GetPropertyStr(ctx, href, cstring(s))
+    let res = JS_GetPropertyStr(ctx, href, cstring(sa.toStr()))
     JS_FreeValue(ctx, href)
     return res
   if sa == satProtocol:
@@ -2915,8 +2868,8 @@ proc hyperlinkSet(ctx: JSContext; this, val: JSValue; magic: cint): JSValue
   let url = element.reinitURL()
   if url.isSome:
     let href = ctx.toJS(url)
-    let s = ctx.toStr(sa)
-    let res = JS_SetPropertyStr(ctx, href, cstring(s), JS_DupValue(ctx, val))
+    let res = JS_SetPropertyStr(ctx, href, cstring(sa.toStr()),
+      JS_DupValue(ctx, val))
     if res < 0:
       return JS_EXCEPTION
     var outs: string
@@ -2929,7 +2882,7 @@ proc hyperlinkGetProp(ctx: JSContext; element: HTMLElement; a: JSAtom;
     desc: ptr JSPropertyDescriptor): JSValue =
   var s: string
   if ctx.fromJS(a, s).isSome:
-    let sa = ctx.toStaticAtom(ctx.toAtom(s))
+    let sa = s.toStaticAtom()
     if sa in {satHref, satOrigin, satProtocol, satUsername, satPassword,
         satHost, satHostname, satPort, satPathname, satSearch, satHash}:
       if desc != nil:
@@ -3099,7 +3052,7 @@ func inputString*(input: HTMLInputElement): CharacterData =
     return input.internalValue
 
 # <label>
-func control*(label: HTMLLabelElement): FormAssociatedElement {.jsfget.} =
+proc control*(label: HTMLLabelElement): FormAssociatedElement {.jsfget.} =
   let f = label.attr(satFor)
   if f != "":
     let elem = label.document.getElementById(f)
@@ -3113,7 +3066,7 @@ func control*(label: HTMLLabelElement): FormAssociatedElement {.jsfget.} =
     return nil
   return nil
 
-func form(label: HTMLLabelElement): HTMLFormElement {.jsfget.} =
+proc form(label: HTMLLabelElement): HTMLFormElement {.jsfget.} =
   let control = label.control
   if control != nil:
     return control.form
@@ -3376,8 +3329,8 @@ proc updateSheet*(this: HTMLStyleElement) =
   let document = this.document
   let window = document.window
   if window != nil:
-    this.sheet = this.textContent.parseStylesheet(document.factory,
-      document.baseURL, window.attrsp).applyMediaQuery(window)
+    this.sheet = this.textContent.parseStylesheet(document.baseURL,
+      window.attrsp).applyMediaQuery(window)
     document.applyAuthorSheets()
 
 # <table>
@@ -3642,7 +3595,7 @@ func newComment(ctx: JSContext; data: sink string = ""): Comment {.jsctor.} =
 proc newElement*(document: Document; localName, namespaceURI, prefix: CAtom):
     Element =
   let tagType = localName.toTagType()
-  let sns = document.toStaticAtom(namespaceURI)
+  let sns = namespaceURI.toStaticAtom()
   let element: Element = case tagType
   of TAG_INPUT:
     HTMLInputElement()
@@ -3755,31 +3708,25 @@ proc newElement*(document: Document; localName, namespaceURI, prefix: CAtom):
 
 proc newElement*(document: Document; localName: CAtom;
     namespace = Namespace.HTML; prefix = NO_PREFIX): Element =
-  return document.newElement(localName, document.toAtom(namespace),
-    document.toAtom(prefix))
+  return document.newElement(localName, namespace.toAtom(), prefix.toAtom())
 
 proc newHTMLElement*(document: Document; tagType: TagType): HTMLElement =
-  let localName = document.toAtom(tagType)
+  let localName = tagType.toAtom()
   return HTMLElement(document.newElement(localName, Namespace.HTML, NO_PREFIX))
 
-proc newDocument*(factory: CAtomFactory): Document =
+proc newDocument*(): Document {.jsctor.} =
   let document = Document(
     url: newURL("about:blank").get,
     index: -1,
-    factory: factory,
     contentType: "application/xml"
   )
   document.implementation = DOMImplementation(document: document)
   return document
 
-proc newDocument(ctx: JSContext): Document {.jsctor.} =
-  return newDocument(ctx.getGlobal().factory)
-
-proc newXMLDocument(ctx: JSContext): XMLDocument =
+proc newXMLDocument(): XMLDocument =
   let document = XMLDocument(
     url: newURL("about:blank").get,
     index: -1,
-    factory: ctx.getGlobal().factory,
     contentType: "application/xml"
   )
   document.implementation = DOMImplementation(document: document)
@@ -4021,8 +3968,7 @@ proc setValue(this: CSSStyleDeclaration; i: int; cvals: seq[CSSComponentValue]):
     return err()
   # dummyAttrs can be safely used because the result is discarded.
   var dummy: seq[CSSComputedEntry] = @[]
-  ?dummy.parseComputedValues(this.decls[i].name, cvals, dummyAttrs,
-    this.element.document.factory)
+  ?dummy.parseComputedValues(this.decls[i].name, cvals, dummyAttrs)
   this.decls[i].value = cvals
   return ok()
 
@@ -4061,8 +4007,7 @@ proc setProperty(this: CSSStyleDeclaration; name, value: string):
       return ok()
   else:
     var dummy: seq[CSSComputedEntry] = @[]
-    let val0 = dummy.parseComputedValues(name, cvals, dummyAttrs,
-      this.element.document.factory)
+    let val0 = dummy.parseComputedValues(name, cvals, dummyAttrs)
     if val0.isNone:
       return ok()
     this.decls.add(CSSDeclaration(name: name, value: cvals))
@@ -4123,7 +4068,7 @@ proc loadSheet(window: Window; link: HTMLLinkElement; url: URL):
     return newResolvedPromise(JSResult[string].err(nil))
   ).then(proc(s: JSResult[string]): Promise[CSSStylesheet] =
     if s.isSome:
-      let sheet = s.get.parseStylesheet(window.factory, url, window.attrsp)
+      let sheet = s.get.parseStylesheet(url, window.attrsp)
       var promises: seq[EmptyPromise] = @[]
       var sheets = newSeq[CSSStylesheet](sheet.importList.len)
       for i, url in sheet.importList:
@@ -4389,11 +4334,11 @@ proc reflectEvent(element: Element; target: EventTarget;
     # directly here, but a wrapper function that calls fun. Currently
     # you can run removeEventListener with element.onclick, that should
     # not work.
-    doAssert ctx.addEventListener(target, document.toAtom(ctype), fun).isSome
+    doAssert ctx.addEventListener(target, ctype.toAtom(), fun).isSome
   JS_FreeValue(ctx, fun)
 
 proc reflectAttr(element: Element; name: CAtom; value: Option[string]) =
-  let name = element.document.toStaticAtom(name)
+  let name = name.toStaticAtom()
   template reflect_str(element: Element; n: StaticAtom; val: untyped) =
     if name == n:
       element.val = value.get("")
@@ -4401,7 +4346,7 @@ proc reflectAttr(element: Element; name: CAtom; value: Option[string]) =
   template reflect_atom(element: Element; n: StaticAtom; val: untyped) =
     if name == n:
       if value.isSome:
-        element.val = element.document.toAtom(value.get)
+        element.val = value.get.toAtom()
       else:
         element.val = CAtomNull
       return
@@ -4414,7 +4359,7 @@ proc reflectAttr(element: Element; name: CAtom; value: Option[string]) =
     if value.isSome:
       for x in value.get.split(AsciiWhitespace):
         if x != "":
-          let a = element.document.toAtom(x)
+          let a = x.toAtom()
           if a notin element.val:
             element.val.toks.add(a)
   template reflect_domtoklist(element: Element; n: StaticAtom; val: untyped) =
@@ -4543,20 +4488,20 @@ proc attr*(element: Element; name: CAtom; value: sink string) =
   element.reflectAttr(name, some(element.attrs[i].value))
 
 proc attr*(element: Element; name: StaticAtom; value: sink string) =
-  element.attr(element.document.toAtom(name), value)
+  element.attr(name.toAtom(), value)
 
 proc attrns*(element: Element; localName: CAtom; prefix: NamespacePrefix;
     namespace: Namespace; value: sink string) =
   if prefix == NO_PREFIX and namespace == NO_NAMESPACE:
     element.attr(localName, value)
     return
-  let namespace = element.document.toAtom(namespace)
+  let namespace = namespace.toAtom()
   let i = element.findAttrNS(namespace, localName)
   var prefixAtom, qualifiedName: CAtom
   if prefix != NO_PREFIX:
-    prefixAtom = element.document.toAtom(prefix)
-    let tmp = $prefix & ':' & element.document.toStr(localName)
-    qualifiedName = element.document.toAtom(tmp)
+    prefixAtom = prefix.toAtom()
+    let tmp = $prefix & ':' & localName.toStr()
+    qualifiedName = tmp.toAtom()
   else:
     qualifiedName = localName
   if i != -1:
@@ -4590,9 +4535,9 @@ proc setAttribute(element: Element; qualifiedName: string; value: sink string):
   ?qualifiedName.validateName()
   let qualifiedName = if element.namespace == Namespace.HTML and
       not element.document.isxml:
-    element.document.toAtomLower(qualifiedName)
+    qualifiedName.toAtomLower()
   else:
-    element.document.toAtom(qualifiedName)
+    qualifiedName.toAtom()
   element.attr(qualifiedName, value)
   return ok()
 
@@ -4601,7 +4546,7 @@ proc setAttributeNS(element: Element; namespace, qualifiedName,
   ?qualifiedName.validateQName()
   let ps = qualifiedName.until(':')
   let prefix = if ps.len < qualifiedName.len: ps else: ""
-  let localName = element.document.toAtom(qualifiedName.substr(prefix.len))
+  let localName = qualifiedName.substr(prefix.len).toAtom()
   #TODO atomize here
   if prefix != "" and namespace == "" or
       prefix == "xml" and namespace != $Namespace.XML or
@@ -4610,8 +4555,8 @@ proc setAttributeNS(element: Element; namespace, qualifiedName,
       namespace == $Namespace.XMLNS and qualifiedName != "xmlns" and
         prefix != "xmlns":
     return errDOMException("Unexpected namespace", "NamespaceError")
-  let qualifiedName = element.document.toAtom(qualifiedName)
-  let namespace = element.document.toAtom(namespace)
+  let qualifiedName = qualifiedName.toAtom()
+  let namespace = namespace.toAtom()
   let i = element.findAttrNS(namespace, localName)
   if i != -1:
     element.attrs[i].value = value
@@ -5534,9 +5479,9 @@ proc createElement(document: Document; localName: string): DOMResult[Element]
     {.jsfunc.} =
   ?localName.validateName()
   let localName = if not document.isxml:
-    document.toAtomLower(localName)
+    localName.toAtomLower()
   else:
-    document.toAtom(localName)
+    localName.toAtom()
   let namespace = if not document.isxml:
     #TODO or content type is application/xhtml+xml
     Namespace.HTML
@@ -5547,7 +5492,7 @@ proc createElement(document: Document; localName: string): DOMResult[Element]
 proc validateAndExtract(ctx: JSContext; document: Document; qname: string;
     namespace, prefixOut, localNameOut: var CAtom): DOMResult[void] =
   ?qname.validateQName()
-  if namespace == ctx.toAtom(""):
+  if namespace == satUempty.toAtom():
     namespace = CAtomNull
   var prefix = ""
   var localName = qname.until(':')
@@ -5557,13 +5502,13 @@ proc validateAndExtract(ctx: JSContext; document: Document; qname: string;
   if namespace == CAtomNull and prefix != "":
     return errDOMException("Got namespace prefix, but no namespace",
       "NamespaceError")
-  let sns = document.toStaticAtom(namespace)
+  let sns = namespace.toStaticAtom()
   if prefix == "xml" and sns != satNamespaceXML:
     return errDOMException("Expected XML namespace", "NamespaceError")
   if (qname == "xmlns" or prefix == "xmlns") != (sns == satNamespaceXMLNS):
     return errDOMException("Expected XMLNS namespace", "NamespaceError")
-  prefixOut = if prefix == "": CAtomNull else: document.toAtom(prefix)
-  localNameOut = document.toAtom(localName)
+  prefixOut = if prefix == "": CAtomNull else: prefix.toAtom()
+  localNameOut = localName.toAtom()
   ok()
 
 proc createElementNS(ctx: JSContext; document: Document; namespace: CAtom;
@@ -5586,7 +5531,7 @@ proc createDocumentType(implementation: var DOMImplementation; qualifiedName,
 proc createDocument(ctx: JSContext; implementation: var DOMImplementation;
     namespace: CAtom; qname0 = JS_NULL; doctype = none(DocumentType)):
     DOMResult[XMLDocument] {.jsfunc.} =
-  let document = newXMLDocument(ctx)
+  let document = newXMLDocument()
   var qname = ""
   if not JS_IsNull(qname0):
     ?ctx.fromJS(qname0, qname)
@@ -5599,15 +5544,15 @@ proc createDocument(ctx: JSContext; implementation: var DOMImplementation;
   if element != nil:
     document.append(element)
   document.origin = implementation.document.origin
-  case document.toStaticAtom(namespace)
+  case namespace.toStaticAtom()
   of satNamespaceHTML: document.contentType = "application/xml+html"
   of satNamespaceSVG: document.contentType = "image/svg+xml"
   else: discard
   return ok(document)
 
-proc createHTMLDocument(ctx: JSContext; implementation: var DOMImplementation;
+proc createHTMLDocument(implementation: var DOMImplementation;
     title = none(string)): Document {.jsfunc.} =
-  let doc = newDocument(ctx)
+  let doc = newDocument()
   doc.contentType = "text/html"
   doc.append(doc.newDocumentType("html", "", ""))
   let html = doc.newHTMLElement(TAG_HTML)
@@ -5650,11 +5595,11 @@ proc createProcessingInstruction(document: Document; target, data: string):
 
 proc createEvent(ctx: JSContext; document: Document; atom: CAtom):
     DOMResult[Event] {.jsfunc.} =
-  case ctx.toStaticAtom(ctx.toLowerAscii(atom))
+  case atom.toLowerAscii().toStaticAtom()
   of satCustomevent:
-    return ok(ctx.newCustomEvent(ctx.toAtom("")))
+    return ok(ctx.newCustomEvent(satUempty.toAtom()))
   of satEvent, satEvents, satSvgevents:
-    return ok(newEvent(ctx.toAtom(""), nil))
+    return ok(newEvent(satUempty.toAtom(), nil))
   else:
     return errDOMException("Event not supported", "NotSupportedError")
 
@@ -5716,7 +5661,7 @@ proc clone(node: Node; document = none(Document), deep = false): Node =
     Node(x)
   elif node of Document:
     let document = Document(node)
-    let x = newDocument(document.factory)
+    let x = newDocument()
     x.charset = document.charset
     x.contentType = document.contentType
     x.url = document.url
@@ -5796,7 +5741,7 @@ func isSameNode(node, other: Node): bool {.jsfunc.} =
   return node == other
 
 proc querySelectorImpl(node: Node; q: string): DOMResult[Element] =
-  let selectors = parseSelectors(q, node.document.factory)
+  let selectors = parseSelectors(q)
   if selectors.len == 0:
     return errDOMException("Invalid selector: " & q, "SyntaxError")
   for element in node.elements:
@@ -5815,7 +5760,7 @@ proc querySelector(this: DocumentFragment; q: string): DOMResult[Element]
   return this.querySelectorImpl(q)
 
 proc querySelectorAllImpl(node: Node; q: string): DOMResult[NodeList] =
-  let selectors = parseSelectors(q, node.document.factory)
+  let selectors = parseSelectors(q)
   if selectors.len == 0:
     return errDOMException("Invalid selector: " & q, "SyntaxError")
   return ok(node.newNodeList(
@@ -5935,8 +5880,7 @@ proc jsReflectSet(ctx: JSContext; this, val: JSValue; magic: cint): JSValue
       assert ctx.fromJS(this, target).isSome
       ctx.definePropertyC(this, $entry.attrname, JS_DupValue(ctx, val))
       #TODO I haven't checked but this might also be wrong
-      let ctype = ctx.getGlobal().toAtom(entry.ctype)
-      doAssert ctx.addEventListener(target, ctype, val).isSome
+      doAssert ctx.addEventListener(target, entry.ctype.toAtom(), val).isSome
   return JS_DupValue(ctx, val)
 
 func getReflectFunctions(tags: set[TagType]): seq[TabGetSet] =
@@ -6210,16 +6154,13 @@ getParentImpl = proc(ctx: JSContext; eventTarget: EventTarget; event: Event):
     EventTarget =
   if eventTarget of Node:
     if eventTarget of Document:
-      if event.ctype == ctx.toAtom(satLoad):
+      if event.ctype == satLoad.toAtom():
         return nil
       # if no browsing context, then window will be nil anyway
       return Document(eventTarget).window
     return Node(eventTarget).parentNode
   return nil
 
-getFactoryImpl = proc(ctx: JSContext): CAtomFactory =
-  return ctx.getGlobal().factory
-
 errorImpl = proc(ctx: JSContext; ss: varargs[string]) =
   ctx.getGlobal().console.error(ss)