about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/css/cascade.nim10
-rw-r--r--src/css/cssvalues.nim34
-rw-r--r--src/css/match.nim5
-rw-r--r--src/css/selectorparser.nim35
-rw-r--r--src/css/sheet.nim21
-rw-r--r--src/html/catom.nim103
-rw-r--r--src/html/chadombuilder.nim24
-rw-r--r--src/html/dom.nim299
-rw-r--r--src/html/env.nim9
-rw-r--r--src/html/event.nim15
-rw-r--r--src/html/xmlhttprequest.nim4
-rw-r--r--src/local/client.nim2
-rw-r--r--src/server/buffer.nim18
13 files changed, 238 insertions, 341 deletions
diff --git a/src/css/cascade.nim b/src/css/cascade.nim
index 49fc6647..996b335d 100644
--- a/src/css/cascade.nim
+++ b/src/css/cascade.nim
@@ -76,10 +76,10 @@ proc calcRules(map: var RuleListMap; element: Element;
   sheet.tagTable.withValue(element.localName, v):
     rules.add(v[])
   if element.id != CAtomNull:
-    sheet.idTable.withValue(sheet.factory.toLowerAscii(element.id), v):
+    sheet.idTable.withValue(element.id.toLowerAscii(), v):
       rules.add(v[])
   for class in element.classList:
-    sheet.classTable.withValue(sheet.factory.toLowerAscii(class), v):
+    sheet.classTable.withValue(class.toLowerAscii(), v):
       rules.add(v[])
   for attr in element.attrs:
     sheet.attrTable.withValue(attr.qualifiedName, v):
@@ -121,8 +121,7 @@ proc applyVariable(ctx: var ApplyValueContext; t: CSSPropertyType;
       ctx.applyValue0(entry, initType, nextInitType)
       return
   var entries: seq[CSSComputedEntry] = @[]
-  if entries.parseComputedValues($t, cvar.cvals, ctx.window.attrsp[],
-      ctx.window.factory).isSome:
+  if entries.parseComputedValues($t, cvar.cvals, ctx.window.attrsp[]).isSome:
     if entries[0].et == ceVar:
       if ctx.varsSeen.containsOrIncl(varName) or ctx.varsSeen.len > 20:
         ctx.varsSeen.clear()
@@ -373,8 +372,7 @@ proc applyStyle*(element: Element) =
   if window.styling and style != nil:
     for decl in style.decls:
       #TODO variables
-      let vals = parseComputedValues(decl.name, decl.value, window.attrsp[],
-        window.factory)
+      let vals = parseComputedValues(decl.name, decl.value, window.attrsp[])
       if decl.important:
         map[peNone][coAuthor].important.add(vals)
       else:
diff --git a/src/css/cssvalues.nim b/src/css/cssvalues.nim
index 00011cc5..a183d6fb 100644
--- a/src/css/cssvalues.nim
+++ b/src/css/cssvalues.nim
@@ -556,8 +556,7 @@ const WhiteSpacePreserve* = {
 
 # Forward declarations
 proc parseValue(cvals: openArray[CSSComponentValue]; t: CSSPropertyType;
-  entry: var CSSComputedEntry; attrs: WindowAttributes; factory: CAtomFactory):
-  Opt[void]
+  entry: var CSSComputedEntry; attrs: WindowAttributes): Opt[void]
 
 proc newCSSVariableMap*(parent: CSSVariableMap): CSSVariableMap =
   return CSSVariableMap(parent: parent)
@@ -610,7 +609,7 @@ func `$`(quotes: CSSQuotes): string =
 func `$`(counterreset: seq[CSSCounterReset]): string =
   result = ""
   for it in counterreset:
-    result &= $it.name
+    result &= it.name
     result &= ' '
     result &= $it.num
 
@@ -1445,8 +1444,7 @@ proc makeEntry*(t: CSSPropertyType; global: CSSGlobalType): CSSComputedEntry =
   return CSSComputedEntry(et: ceGlobal, t: t, global: global)
 
 proc parseVariable(fun: CSSFunction; t: CSSPropertyType;
-    entry: var CSSComputedEntry; attrs: WindowAttributes;
-    factory: CAtomFactory): Opt[void] =
+    entry: var CSSComputedEntry; attrs: WindowAttributes): Opt[void] =
   var i = fun.value.skipBlanks(0)
   if i >= fun.value.len:
     return err()
@@ -1459,7 +1457,7 @@ proc parseVariable(fun: CSSFunction; t: CSSPropertyType;
   entry = CSSComputedEntry(
     et: ceVar,
     t: t,
-    cvar: factory.toAtom(tok.value.substr(2))
+    cvar: tok.value.substr(2).toAtom()
   )
   i = fun.value.skipBlanks(i + 1)
   if i < fun.value.len:
@@ -1469,13 +1467,12 @@ proc parseVariable(fun: CSSFunction; t: CSSPropertyType;
     if i < fun.value.len:
       entry.fallback = (ref CSSComputedEntry)()
       if fun.value.toOpenArray(i, fun.value.high).parseValue(t,
-          entry.fallback[], attrs, factory).isNone:
+          entry.fallback[], attrs).isNone:
         entry.fallback = nil
   return ok()
 
 proc parseValue(cvals: openArray[CSSComponentValue]; t: CSSPropertyType;
-    entry: var CSSComputedEntry; attrs: WindowAttributes;
-    factory: CAtomFactory): Opt[void] =
+    entry: var CSSComputedEntry; attrs: WindowAttributes): Opt[void] =
   var i = cvals.skipBlanks(0)
   if i >= cvals.len:
     return err()
@@ -1486,7 +1483,7 @@ proc parseValue(cvals: openArray[CSSComponentValue]; t: CSSPropertyType;
     if fun.name == cftVar:
       if cvals.skipBlanks(i) < cvals.len:
         return err()
-      return fun.parseVariable(t, entry, attrs, factory)
+      return fun.parseVariable(t, entry, attrs)
   let v = valueType(t)
   template set_new(prop, val: untyped) =
     entry = CSSComputedEntry(
@@ -1666,8 +1663,7 @@ proc addGlobals(res: var seq[CSSComputedEntry]; ps: openArray[CSSPropertyType];
     res.add(makeEntry(p, global))
 
 proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string;
-    cvals: openArray[CSSComponentValue]; attrs: WindowAttributes;
-    factory: CAtomFactory): Err[void] =
+    cvals: openArray[CSSComponentValue]; attrs: WindowAttributes): Err[void] =
   var i = cvals.skipBlanks(0)
   if i >= cvals.len:
     return err()
@@ -1681,7 +1677,7 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string;
         res.add(makeEntry(t, global.get))
       else:
         var entry = CSSComputedEntry()
-        ?cvals.parseValue(t, entry, attrs, factory)
+        ?cvals.parseValue(t, entry, attrs)
         res.add(entry)
   of cstAll:
     let global = ?global
@@ -1704,11 +1700,11 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string;
       var i = cvals.skipBlanks(0)
       while i < cvals.len:
         let j = cvals.findBlank(i)
-        if cvals.toOpenArray(i, j - 1).parseValue(bgcolor.t, bgcolor, attrs,
-            factory).isSome:
+        if cvals.toOpenArray(i, j - 1).parseValue(bgcolor.t, bgcolor,
+            attrs).isSome:
           discard
-        elif cvals.toOpenArray(i, j - 1).parseValue(bgimage.t, bgimage, attrs,
-            factory).isSome:
+        elif cvals.toOpenArray(i, j - 1).parseValue(bgimage.t, bgimage,
+            attrs).isSome:
           discard
         else:
           #TODO when we implement the other shorthands too
@@ -1806,9 +1802,9 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string;
   return ok()
 
 proc parseComputedValues*(name: string; value: seq[CSSComponentValue];
-    attrs: WindowAttributes; factory: CAtomFactory): seq[CSSComputedEntry] =
+    attrs: WindowAttributes): seq[CSSComputedEntry] =
   var res: seq[CSSComputedEntry] = @[]
-  if res.parseComputedValues(name, value, attrs, factory).isSome:
+  if res.parseComputedValues(name, value, attrs).isSome:
     return res
   return @[]
 
diff --git a/src/css/match.nim b/src/css/match.nim
index bb1acd9c..065d34cf 100644
--- a/src/css/match.nim
+++ b/src/css/match.nim
@@ -185,13 +185,12 @@ func matches(element: Element; sel: Selector; depends: var DependencyInfo):
   of stType:
     return element.localName == sel.tag
   of stClass:
-    let factory = element.document.factory
     for it in element.classList:
-      if sel.class == factory.toLowerAscii(it):
+      if sel.class == it.toLowerAscii():
         return mtTrue
     return mtFalse
   of stId:
-    return sel.id == element.document.factory.toLowerAscii(element.id)
+    return sel.id == element.id.toLowerAscii()
   of stAttr:
     return element.matchesAttr(sel)
   of stPseudoClass:
diff --git a/src/css/selectorparser.nim b/src/css/selectorparser.nim
index 1aaa9793..015ac0bb 100644
--- a/src/css/selectorparser.nim
+++ b/src/css/selectorparser.nim
@@ -41,7 +41,6 @@ type
     selectors: seq[ComplexSelector]
     cvals: seq[CSSComponentValue]
     at: int
-    factory: CAtomFactory
     failed: bool
     nested: bool
 
@@ -106,8 +105,8 @@ type
   SelectorList* = seq[ComplexSelector]
 
 # Forward declarations
-proc parseSelectorList(cvals: seq[CSSComponentValue]; factory: CAtomFactory;
-  nested, forgiving: bool): SelectorList
+proc parseSelectorList(cvals: seq[CSSComponentValue]; nested, forgiving: bool):
+  SelectorList
 proc parseComplexSelector(state: var SelectorParser): ComplexSelector
 func `$`*(cxsel: ComplexSelector): string
 
@@ -295,8 +294,7 @@ proc parseRecursiveSelectorFunction(state: var SelectorParser;
     t: stPseudoClass,
     pseudo: PseudoData(t: class),
   )
-  fun.pseudo.fsels = parseSelectorList(body, state.factory, nested = true,
-    forgiving)
+  fun.pseudo.fsels = parseSelectorList(body, nested = true, forgiving)
   if fun.pseudo.fsels.len == 0: fail
   return fun
 
@@ -319,7 +317,7 @@ proc parseNthChild(state: var SelectorParser; cssfunction: CSSFunction;
     inc i
   if i == cssfunction.value.len: fail
   nthchild.pseudo.ofsels = cssfunction.value[i..^1]
-    .parseSelectorList(state.factory, nested = true, forgiving = false)
+    .parseSelectorList(nested = true, forgiving = false)
   if nthchild.pseudo.ofsels.len == 0: fail
   return nthchild
 
@@ -410,7 +408,7 @@ proc parseAttributeSelector(state: var SelectorParser;
   if not state2.has():
     return Selector(
       t: stAttr,
-      attr: state.factory.toAtomLower(attr.value),
+      attr: attr.value.toAtomLower(),
       rel: SelectorRelation(t: rtExists)
     )
   let delim = get_tok state2.consume()
@@ -441,7 +439,7 @@ proc parseAttributeSelector(state: var SelectorParser;
       flag = rfS
   return Selector(
     t: stAttr,
-    attr: state.factory.toAtomLower(attr.value),
+    attr: attr.value.toAtomLower(),
     value: value.value,
     rel: SelectorRelation(t: rel, flag: flag)
   )
@@ -450,7 +448,7 @@ proc parseClassSelector(state: var SelectorParser): Selector =
   if not state.has(): fail
   let tok = get_tok state.consume()
   if tok.t != cttIdent: fail
-  let class = state.factory.toAtomLower(tok.value)
+  let class = tok.value.toAtomLower()
   result = Selector(t: stClass, class: class)
   when defined(debug):
     result.classs = tok.value
@@ -464,7 +462,7 @@ proc parseCompoundSelector(state: var SelectorParser): CompoundSelector =
       case tok.t
       of cttIdent:
         inc state.at
-        let tag = state.factory.toAtomLower(tok.value)
+        let tag = tok.value.toAtomLower()
         let sel = Selector(t: stType, tag: tag)
         when defined(debug):
           sel.tags = tok.value
@@ -474,7 +472,7 @@ proc parseCompoundSelector(state: var SelectorParser): CompoundSelector =
         result.add(state.parsePseudoSelector())
       of cttHash:
         inc state.at
-        let id = state.factory.toAtomLower(tok.value)
+        let id = tok.value.toAtomLower()
         result.add(Selector(t: stId, id: id))
         when defined(debug):
           result[^1].ids = tok.value
@@ -537,11 +535,10 @@ proc parseComplexSelector(state: var SelectorParser): ComplexSelector =
     #TODO move pseudo check here?
     result.pseudo = result[^1][^1].elem
 
-proc parseSelectorList(cvals: seq[CSSComponentValue]; factory: CAtomFactory;
-    nested, forgiving: bool): SelectorList =
+proc parseSelectorList(cvals: seq[CSSComponentValue]; nested, forgiving: bool):
+    SelectorList =
   var state = SelectorParser(
     cvals: cvals,
-    factory: factory,
     nested: nested
   )
   var res: SelectorList = @[]
@@ -557,10 +554,8 @@ proc parseSelectorList(cvals: seq[CSSComponentValue]; factory: CAtomFactory;
       res.add(csel)
   return res
 
-proc parseSelectors*(cvals: seq[CSSComponentValue]; factory: CAtomFactory):
-    seq[ComplexSelector] =
-  return parseSelectorList(cvals, factory, nested = false, forgiving = false)
+proc parseSelectors*(cvals: seq[CSSComponentValue]): seq[ComplexSelector] =
+  return parseSelectorList(cvals, nested = false, forgiving = false)
 
-proc parseSelectors*(ibuf: string; factory: CAtomFactory):
-    seq[ComplexSelector] =
-  return parseSelectors(parseComponentValues(ibuf), factory)
+proc parseSelectors*(ibuf: string): seq[ComplexSelector] =
+  return parseSelectors(parseComponentValues(ibuf))
diff --git a/src/css/sheet.nim b/src/css/sheet.nim
index ed15fe76..b1abfa5e 100644
--- a/src/css/sheet.nim
+++ b/src/css/sheet.nim
@@ -39,7 +39,6 @@ type
     generalList*: seq[CSSRuleDef]
     importList*: seq[URL]
     len: int
-    factory*: CAtomFactory
     attrs: ptr WindowAttributes
 
 type SelectorHashes = object
@@ -48,8 +47,7 @@ type SelectorHashes = object
   class: CAtom
   attr: CAtom
 
-func newStylesheet*(cap: int; factory: CAtomFactory;
-    attrs: ptr WindowAttributes): CSSStylesheet =
+func newStylesheet*(cap: int; attrs: ptr WindowAttributes): CSSStylesheet =
   let bucketsize = cap div 2
   return CSSStylesheet(
     tagTable: initTable[CAtom, seq[CSSRuleDef]](bucketsize),
@@ -57,7 +55,6 @@ func newStylesheet*(cap: int; factory: CAtomFactory;
     classTable: initTable[CAtom, seq[CSSRuleDef]](bucketsize),
     attrTable: initTable[CAtom, seq[CSSRuleDef]](bucketsize),
     generalList: newSeqOfCap[CSSRuleDef](bucketsize),
-    factory: factory,
     attrs: attrs
   )
 
@@ -183,14 +180,14 @@ proc add*(sheet, sheet2: CSSStylesheet) =
       sheet.attrTable[key] = value
 
 proc addRule(sheet: CSSStylesheet; rule: CSSQualifiedRule) =
-  var sels = parseSelectors(rule.prelude, sheet.factory)
+  var sels = parseSelectors(rule.prelude)
   if sels.len > 0:
     let decls = rule.oblock.value.parseDeclarations()
     let rule = CSSRuleDef(sels: move(sels), idx: sheet.len)
     for decl in decls:
       if decl.name.startsWith("--"):
         let cvar = CSSVariable(
-          name: sheet.factory.toAtom(decl.name.substr(2)),
+          name: decl.name.substr(2).toAtom(),
           cvals: decl.value
         )
         if decl.important:
@@ -201,12 +198,12 @@ proc addRule(sheet: CSSStylesheet; rule: CSSQualifiedRule) =
         if decl.important:
           let olen = rule.importantVals.len
           if rule.importantVals.parseComputedValues(decl.name, decl.value,
-              sheet.attrs[], sheet.factory).isNone:
+              sheet.attrs[]).isNone:
             rule.importantVals.setLen(olen)
         else:
           let olen = rule.normalVals.len
           if rule.normalVals.parseComputedValues(decl.name, decl.value,
-              sheet.attrs[], sheet.factory).isNone:
+              sheet.attrs[]).isNone:
             rule.normalVals.setLen(olen)
     sheet.add(rule)
     inc sheet.len
@@ -232,7 +229,7 @@ proc addAtRule(sheet: CSSStylesheet; atrule: CSSAtRule; base: URL) =
       let rules = atrule.oblock.value.parseListOfRules()
       if rules.len > 0:
         var media = CSSMediaQueryDef()
-        media.children = newStylesheet(rules.len, sheet.factory, sheet.attrs)
+        media.children = newStylesheet(rules.len, sheet.attrs)
         media.children.len = sheet.len
         media.query = query
         for rule in rules:
@@ -243,10 +240,10 @@ proc addAtRule(sheet: CSSStylesheet; atrule: CSSAtRule; base: URL) =
         sheet.mqList.add(media)
         sheet.len = media.children.len
 
-proc parseStylesheet*(ibuf: string; factory: CAtomFactory; base: URL;
-    attrs: ptr WindowAttributes): CSSStylesheet =
+proc parseStylesheet*(ibuf: string; base: URL; attrs: ptr WindowAttributes):
+    CSSStylesheet =
   let raw = parseStylesheet(ibuf)
-  let sheet = newStylesheet(raw.value.len, factory, attrs)
+  let sheet = newStylesheet(raw.value.len, attrs)
   for v in raw.value:
     if v of CSSAtRule:
       sheet.addAtRule(CSSAtRule(v), base)
diff --git a/src/html/catom.nim b/src/html/catom.nim
index e4893125..c888333e 100644
--- a/src/html/catom.nim
+++ b/src/html/catom.nim
@@ -229,48 +229,56 @@ const factoryInit = (func(): CAtomFactoryInit =
   return init
 )()
 
-proc newCAtomFactory*(): CAtomFactory =
+proc newCAtomFactory(): CAtomFactory =
   let factory = new(CAtomFactory)
   factory[] = factoryInit.obj
   return factory
 
-func toLowerAscii*(factory: CAtomFactory; a: CAtom): CAtom =
-  return factory.lowerMap[int32(a)]
+var factory {.global.}: CAtomFactory = nil
 
-func equalsIgnoreCase*(factory: CAtomFactory; a, b: CAtom): bool =
-  return factory.lowerMap[int32(a)] == factory.lowerMap[int32(b)]
+func getFactory(): CAtomFactory =
+  {.cast(noSideEffect).}:
+    return factory
 
-func containsIgnoreCase*(factory: CAtomFactory; aa: openArray[CAtom];
-    a: CAtom): bool =
-  let a = factory.toLowerAscii(a)
+proc initCAtomFactory*() =
+  assert factory == nil
+  factory = newCAtomFactory()
+
+func toLowerAscii*(a: CAtom): CAtom =
+  return getFactory().lowerMap[int32(a)]
+
+func equalsIgnoreCase*(a, b: CAtom): bool =
+  return getFactory().lowerMap[int32(a)] == getFactory().lowerMap[int32(b)]
+
+func containsIgnoreCase*(aa: openArray[CAtom]; a: CAtom): bool =
+  let a = a.toLowerAscii()
   for it in aa:
-    if a == factory.toLowerAscii(it):
+    if a == it.toLowerAscii():
       return true
   return false
 
-func toAtom*(factory: CAtomFactory; s: sink string): CAtom =
-  return factory[].toAtom(s)
+proc toAtom*(s: sink string): CAtom {.sideEffect.} =
+  return getFactory()[].toAtom(s)
 
-func toAtom*(factory: CAtomFactory; tagType: TagType): CAtom =
+func toAtom*(tagType: TagType): CAtom =
   assert tagType != TAG_UNKNOWN
   return CAtom(tagType)
 
-func toAtom*(factory: CAtomFactory; attrType: StaticAtom): CAtom =
+func toAtom*(attrType: StaticAtom): CAtom =
   assert attrType != atUnknown
   return CAtom(attrType)
 
-func toAtomLower*(factory: CAtomFactory; s: sink string): CAtom =
-  return factory.lowerMap[int32(factory.toAtom(s))]
+proc toAtomLower*(s: sink string): CAtom {.sideEffect.} =
+  return getFactory().lowerMap[int32(s.toAtom())]
 
-func containsIgnoreCase*(factory: CAtomFactory; aa: openArray[CAtom];
-    a: StaticAtom): bool =
-  return factory.containsIgnoreCase(aa, factory.toAtom(a))
+func containsIgnoreCase*(aa: openArray[CAtom]; a: StaticAtom): bool =
+  return aa.containsIgnoreCase(a.toAtom())
 
-func toStr*(factory: CAtomFactory; atom: CAtom): lent string =
-  return factory.atomMap[int(atom)]
+func toStr*(atom: CAtom): lent string =
+  return getFactory().atomMap[int(atom)]
 
-func toStr*(factory: CAtomFactory; sa: StaticAtom): lent string =
-  return factory.toStr(factory.toAtom(sa))
+func toStr*(sa: StaticAtom): lent string =
+  return sa.toAtom().toStr()
 
 func toTagType*(atom: CAtom): TagType =
   let i = int(atom)
@@ -278,17 +286,17 @@ func toTagType*(atom: CAtom): TagType =
     return TagType(i)
   return TAG_UNKNOWN
 
-func toStaticAtom*(factory: CAtomFactory; atom: CAtom): StaticAtom =
+func toStaticAtom*(atom: CAtom): StaticAtom =
   let i = int(atom)
   if i <= int(StaticAtom.high):
     return StaticAtom(i)
   return atUnknown
 
-func toStaticAtom*(factory: CAtomFactory; s: string): StaticAtom =
-  return factory.toStaticAtom(factory.toAtom(s))
+proc toStaticAtom*(s: string): StaticAtom =
+  return s.toAtom().toStaticAtom()
 
-func toNamespace*(factory: CAtomFactory; atom: CAtom): Namespace =
-  case factory.toStaticAtom(atom)
+func toNamespace*(atom: CAtom): Namespace =
+  case atom.toStaticAtom()
   of satUempty: return NO_NAMESPACE
   of satNamespaceHTML: return Namespace.HTML
   of satNamespaceMathML: return Namespace.MATHML
@@ -298,36 +306,11 @@ func toNamespace*(factory: CAtomFactory; atom: CAtom): Namespace =
   of satNamespaceXMLNS: return Namespace.XMLNS
   else: return NAMESPACE_UNKNOWN
 
-func toAtom*(factory: CAtomFactory; namespace: Namespace): CAtom =
-  return factory.namespaceMap[namespace]
-
-func toAtom*(factory: CAtomFactory; prefix: NamespacePrefix): CAtom =
-  return factory.prefixMap[prefix]
-
-# Forward declaration hack
-var getFactoryImpl*: proc(ctx: JSContext): CAtomFactory {.nimcall, noSideEffect,
-  raises: [].}
-
-proc toAtom*(ctx: JSContext; atom: StaticAtom): CAtom =
-  return ctx.getFactoryImpl().toAtom(atom)
-
-proc toAtom*(ctx: JSContext; s: string): CAtom =
-  return ctx.getFactoryImpl().toAtom(s)
-
-proc toStaticAtom*(ctx: JSContext; atom: CAtom): StaticAtom =
-  return ctx.getFactoryImpl().toStaticAtom(atom)
-
-proc toStaticAtom*(ctx: JSContext; s: string): StaticAtom =
-  return ctx.getFactoryImpl().toStaticAtom(s)
-
-proc toStr*(ctx: JSContext; atom: CAtom): lent string =
-  return ctx.getFactoryImpl().toStr(atom)
-
-proc toLowerAscii*(ctx: JSContext; atom: CAtom): CAtom =
-  return ctx.getFactoryImpl().toLowerAscii(atom)
+func toAtom*(namespace: Namespace): CAtom =
+  return getFactory().namespaceMap[namespace]
 
-proc toStr*(ctx: JSContext; sa: StaticAtom): lent string =
-  return ctx.getFactoryImpl().toStr(sa)
+func toAtom*(prefix: NamespacePrefix): CAtom =
+  return getFactory().prefixMap[prefix]
 
 proc fromJS*(ctx: JSContext; val: JSValue; res: var CAtom): Opt[void] =
   if JS_IsNull(val):
@@ -335,7 +318,7 @@ proc fromJS*(ctx: JSContext; val: JSValue; res: var CAtom): Opt[void] =
   else:
     var s: string
     ?ctx.fromJS(val, s)
-    res = ctx.getFactoryImpl().toAtom(s)
+    res = s.toAtom()
   return ok()
 
 proc fromJS*(ctx: JSContext; val: JSAtom; res: var CAtom): Opt[void] =
@@ -344,19 +327,19 @@ proc fromJS*(ctx: JSContext; val: JSAtom; res: var CAtom): Opt[void] =
   else:
     var s: string
     ?ctx.fromJS(val, s)
-    res = ctx.getFactoryImpl().toAtom(s)
+    res = s.toAtom()
   return ok()
 
 proc fromJS*(ctx: JSContext; val: JSAtom; res: var StaticAtom): Opt[void] =
   var ca: CAtom
   ?ctx.fromJS(val, ca)
-  res = ctx.getFactoryImpl().toStaticAtom(ca)
+  res = ca.toStaticAtom()
   return ok()
 
 proc toJS*(ctx: JSContext; atom: CAtom): JSValue =
   if atom == CAtomNull:
     return JS_NULL
-  return ctx.toJS(ctx.getFactoryImpl().toStr(atom))
+  return ctx.toJS(atom.toStr())
 
 proc toJS*(ctx: JSContext; atom: StaticAtom): JSValue =
   return ctx.toJS($atom)
diff --git a/src/html/chadombuilder.nim b/src/html/chadombuilder.nim
index add5b55d..b6eaab9b 100644
--- a/src/html/chadombuilder.nim
+++ b/src/html/chadombuilder.nim
@@ -33,7 +33,6 @@ type
     charset*: Charset
     confidence*: CharsetConfidence
     document*: Document
-    factory: CAtomFactory
     poppedScript: HTMLScriptElement
 
   DOMBuilderImpl = ChaDOMBuilder
@@ -56,10 +55,10 @@ proc atomToTagTypeImpl(builder: ChaDOMBuilder; atom: CAtom): TagType =
   return atom.toTagType()
 
 proc tagTypeToAtomImpl(builder: ChaDOMBuilder; tagType: TagType): CAtom =
-  return builder.factory.toAtom(tagType)
+  return tagType.toAtom()
 
 proc strToAtomImpl(builder: ChaDOMBuilder; s: string): CAtom =
-  return builder.factory.toAtom(s)
+  return s.toAtom()
 
 proc finish(builder: ChaDOMBuilder) =
   while builder.document.scriptsToExecOnLoad.len > 0:
@@ -70,7 +69,7 @@ proc finish(builder: ChaDOMBuilder) =
 
 proc restart*(wrapper: HTML5ParserWrapper; charset: Charset) =
   let builder = wrapper.builder
-  let document = newDocument(builder.factory)
+  let document = newDocument()
   document.charset = charset
   document.setActiveParser(wrapper)
   document.contentType = "text/html"
@@ -82,7 +81,6 @@ proc restart*(wrapper: HTML5ParserWrapper; charset: Charset) =
     window.document = document
   builder.document = document
   builder.charset = charset
-  assert document.factory != nil
   wrapper.parser = initHTML5Parser(builder, wrapper.opts)
 
 proc setQuirksModeImpl(builder: ChaDOMBuilder; quirksMode: QuirksMode) =
@@ -219,9 +217,9 @@ proc elementPoppedImpl(builder: ChaDOMBuilder; element: Node) =
   elif element of HTMLStyleElement:
     HTMLStyleElement(element).updateSheet()
 
-proc newChaDOMBuilder(url: URL; window: Window; factory: CAtomFactory;
-    confidence: CharsetConfidence; charset = DefaultCharset): ChaDOMBuilder =
-  let document = newDocument(factory)
+proc newChaDOMBuilder(url: URL; window: Window; confidence: CharsetConfidence;
+    charset = DefaultCharset): ChaDOMBuilder =
+  let document = newDocument()
   document.charset = charset
   document.contentType = "text/html"
   document.url = url
@@ -230,7 +228,6 @@ proc newChaDOMBuilder(url: URL; window: Window; factory: CAtomFactory;
     window.document = document
   return ChaDOMBuilder(
     document: document,
-    factory: factory,
     confidence: confidence,
     charset: charset
   )
@@ -238,8 +235,7 @@ proc newChaDOMBuilder(url: URL; window: Window; factory: CAtomFactory;
 # https://html.spec.whatwg.org/multipage/parsing.html#parsing-html-fragments
 proc parseHTMLFragment*(element: Element; s: string): seq[Node] =
   let url = parseURL("about:blank").get
-  let factory = element.document.factory
-  let builder = newChaDOMBuilder(url, nil, factory, ccIrrelevant)
+  let builder = newChaDOMBuilder(url, nil, ccIrrelevant)
   let document = builder.document
   document.mode = element.document.mode
   let state = case element.tagType
@@ -272,12 +268,12 @@ proc parseHTMLFragment*(element: Element; s: string): seq[Node] =
   builder.finish()
   return root.childList
 
-proc newHTML5ParserWrapper*(window: Window; url: URL; factory: CAtomFactory;
+proc newHTML5ParserWrapper*(window: Window; url: URL;
     confidence: CharsetConfidence; charset: Charset): HTML5ParserWrapper =
   let opts = HTML5ParserOpts[Node, CAtom](
     scripting: window.settings.scripting != smFalse
   )
-  let builder = newChaDOMBuilder(url, window, factory, confidence, charset)
+  let builder = newChaDOMBuilder(url, window, confidence, charset)
   let wrapper = HTML5ParserWrapper(
     builder: builder,
     opts: opts,
@@ -368,7 +364,7 @@ proc parseFromString*(ctx: JSContext; parser: DOMParser; str, t: string):
       window.document.url
     else:
       newURL("about:blank").get
-    let builder = newChaDOMBuilder(url, window, window.factory, ccIrrelevant)
+    let builder = newChaDOMBuilder(url, window, ccIrrelevant)
     var parser = initHTML5Parser(builder, HTML5ParserOpts[Node, CAtom]())
     let res = parser.parseChunk(str)
     assert res == PRES_CONTINUE
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)
 
diff --git a/src/html/env.nim b/src/html/env.nim
index a771a33f..f3a7f8d3 100644
--- a/src/html/env.nim
+++ b/src/html/env.nim
@@ -325,7 +325,7 @@ proc postMessage(ctx: JSContext; window: Window; value: JSValue): Err[void]
   ?ctx.fromJS(value, s)
   let data = JS_ParseJSON(ctx, cstring(s), csize_t(s.len),
     cstring"<postMessage>")
-  let event = ctx.newMessageEvent(ctx.toAtom(satMessage),
+  let event = ctx.newMessageEvent(satMessage.toAtom(),
     MessageEventInit(data: data))
   window.fireEvent(event, window)
   ok()
@@ -336,7 +336,7 @@ proc setOnLoad(ctx: JSContext; window: Window; val: JSValue)
     let this = ctx.toJS(window)
     ctx.definePropertyC(this, "onload", JS_DupValue(ctx, val))
     #TODO I haven't checked but this might also be wrong
-    doAssert ctx.addEventListener(window, window.toAtom(satLoad), val).isSome
+    doAssert ctx.addEventListener(window, satLoad.toAtom(), val).isSome
     JS_FreeValue(ctx, this)
 
 proc loadJSModule(ctx: JSContext; moduleName: cstringConst; opaque: pointer):
@@ -423,8 +423,8 @@ proc addScripting*(window: Window) =
   ctx.addPerformanceModule()
 
 proc newWindow*(scripting: ScriptingMode; images, styling, autofocus: bool;
-    attrsp: ptr WindowAttributes; factory: CAtomFactory; loader: FileLoader;
-    url: URL; urandom: PosixStream; imageTypes: Table[string, string];
+    attrsp: ptr WindowAttributes; loader: FileLoader; url: URL;
+    urandom: PosixStream; imageTypes: Table[string, string];
     userAgent, referrer: string): Window =
   let err = newDynFileStream(stderr)
   let window = Window(
@@ -438,7 +438,6 @@ proc newWindow*(scripting: ScriptingMode; images, styling, autofocus: bool;
       scripting: scripting,
       origin: url.origin
     ),
-    factory: factory,
     crypto: Crypto(urandom: urandom),
     imageTypes: imageTypes,
     userAgent: userAgent,
diff --git a/src/html/event.nim b/src/html/event.nim
index b430daa0..11a619ac 100644
--- a/src/html/event.nim
+++ b/src/html/event.nim
@@ -296,10 +296,9 @@ proc newInputEvent*(ctype: CAtom; eventInit = InputEventInit()): InputEvent =
 proc newEventTarget(): EventTarget {.jsctor.} =
   return EventTarget()
 
-proc defaultPassiveValue(ctx: JSContext; ctype: CAtom;
-    eventTarget: EventTarget): bool =
+proc defaultPassiveValue(ctype: CAtom; eventTarget: EventTarget): bool =
   const check = [satTouchstart, satTouchmove, satWheel, satMousewheel]
-  if ctx.toStaticAtom(ctype) in check:
+  if ctype.toStaticAtom() in check:
     return true
   return eventTarget.isDefaultPassiveImpl()
 
@@ -335,13 +334,12 @@ proc invoke(ctx: JSContext; listener: EventListener; event: Event): JSValue =
   return ret
 
 # shared
-proc addAnEventListener(ctx: JSContext; target: EventTarget;
-    listener: EventListener) =
+proc addAnEventListener(target: EventTarget; listener: EventListener) =
   #TODO signals
   if JS_IsUndefined(listener.callback):
     return
   if listener.passive.isNone:
-    listener.passive = some(ctx.defaultPassiveValue(listener.ctype, target))
+    listener.passive = some(defaultPassiveValue(listener.ctype, target))
   if target.findEventListener(listener.ctype, listener.callback,
       listener.capture) == -1: # dedup
     target.eventListeners.add(listener)
@@ -389,14 +387,13 @@ proc addEventListener*(ctx: JSContext; eventTarget: EventTarget; ctype: CAtom;
   if not JS_IsObject(callback) and not JS_IsNull(callback):
     return errTypeError("callback is not an object")
   let (capture, once, passive) = flattenMore(ctx, options)
-  let listener = EventListener(
+  eventTarget.addAnEventListener(EventListener(
     ctype: ctype,
     capture: capture,
     passive: passive,
     once: once,
     callback: JS_DupValue(ctx, callback)
-  )
-  ctx.addAnEventListener(eventTarget, listener)
+  ))
   ok()
 
 proc removeEventListener(ctx: JSContext; eventTarget: EventTarget;
diff --git a/src/html/xmlhttprequest.nim b/src/html/xmlhttprequest.nim
index af3df80b..34602fa6 100644
--- a/src/html/xmlhttprequest.nim
+++ b/src/html/xmlhttprequest.nim
@@ -189,7 +189,7 @@ proc setTimeout(ctx: JSContext; this: XMLHttpRequest; value: uint32):
 
 proc fireProgressEvent(window: Window; target: EventTarget; name: StaticAtom;
     loaded, length: int64) =
-  let event = newProgressEvent(window.factory.toAtom(name), ProgressEventInit(
+  let event = newProgressEvent(name.toAtom(), ProgressEventInit(
     loaded: loaded,
     total: length,
     lengthComputable: length != 0
@@ -503,7 +503,7 @@ proc jsReflectSet(ctx: JSContext; this, val: JSValue; magic: cint): JSValue
     assert ctx.fromJS(this, target).isSome
     ctx.definePropertyC(this, "on" & $atom, JS_DupValue(ctx, val))
     #TODO I haven't checked but this might also be wrong
-    doAssert ctx.addEventListener(target, ctx.toAtom(atom), val).isSome
+    doAssert ctx.addEventListener(target, atom.toAtom(), val).isSome
   return JS_DupValue(ctx, val)
 
 func xhretGetSet(): seq[TabGetSet] =
diff --git a/src/local/client.nim b/src/local/client.nim
index 993bbf0b..355bb389 100644
--- a/src/local/client.nim
+++ b/src/local/client.nim
@@ -161,13 +161,13 @@ func getClient(client: Client): Client {.jsfget: "client".} =
 proc newClient*(config: Config; forkserver: ForkServer; loaderPid: int;
     jsctx: JSContext; warnings: seq[string]; urandom: PosixStream;
     loaderStream: SocketStream): Client =
+  initCAtomFactory()
   let jsrt = JS_GetRuntime(jsctx)
   let clientPid = getCurrentProcessId()
   let loader = newFileLoader(loaderPid, clientPid, loaderStream)
   let client = Client(
     jsrt: jsrt,
     jsctx: jsctx,
-    factory: newCAtomFactory(),
     loader: loader,
     crypto: Crypto(urandom: urandom),
     pager: newPager(config, forkserver, jsctx, warnings, loader),
diff --git a/src/server/buffer.nim b/src/server/buffer.nim
index d20b73fe..49ae89e3 100644
--- a/src/server/buffer.nim
+++ b/src/server/buffer.nim
@@ -85,7 +85,6 @@ type
     ctx: TextDecoderContext
     document: Document
     estream: DynFileStream # error stream
-    factory: CAtomFactory
     fd: int # file descriptor of buffer source
     firstBufferRead: bool
     hoverText: array[HoverType, string]
@@ -331,7 +330,7 @@ const ClickableElements = {
 }
 
 proc isClickable(element: Element): bool =
-  if element.hasEventListener(element.document.toAtom(satClick)):
+  if element.hasEventListener(satClick.toAtom()):
     return true
   if element of HTMLAnchorElement:
     return HTMLAnchorElement(element).reinitURL().isSome
@@ -1020,14 +1019,14 @@ proc clone*(buffer: Buffer; newurl: URL): int {.proxy.} =
 
 proc dispatchDOMContentLoadedEvent(buffer: Buffer) =
   let window = buffer.window
-  let event = newEvent(window.toAtom(satDOMContentLoaded), buffer.document)
+  let event = newEvent(satDOMContentLoaded.toAtom(), buffer.document)
   event.isTrusted = true
   window.fireEvent(event, buffer.document)
   buffer.maybeReshape()
 
 proc dispatchLoadEvent(buffer: Buffer) =
   let window = buffer.window
-  let event = newEvent(window.toAtom(satLoad), window)
+  let event = newEvent(satLoad.toAtom(), window)
   event.isTrusted = true
   window.fireEvent(event, window)
   buffer.maybeReshape()
@@ -1349,7 +1348,7 @@ proc readSuccess*(buffer: Buffer; s: string; hasFd: bool): Request {.proxy.} =
         if input.inputType == itFile:
           window.fireEvent(satInput, input)
         else:
-          let inputEvent = newInputEvent(window.toAtom(satInput),
+          let inputEvent = newInputEvent(satInput.toAtom(),
             InputEventInit(data: some(s), inputType: "insertText"))
           inputEvent.isTrusted = true
           window.fireEvent(inputEvent, input)
@@ -1594,7 +1593,7 @@ proc click*(buffer: Buffer; cursorx, cursory: int): ClickResult {.proxy.} =
     let element = buffer.getCursorElement(cursorx, cursory)
     if element != nil:
       let window = buffer.window
-      let event = newEvent(window.toAtom(satClick), element)
+      let event = newEvent(satClick.toAtom(), element)
       event.isTrusted = true
       canceled = window.jsctx.dispatch(element, event)
       buffer.maybeReshape()
@@ -1909,7 +1908,7 @@ proc cleanup(buffer: Buffer) =
 proc launchBuffer*(config: BufferConfig; url: URL; attrs: WindowAttributes;
     ishtml: bool; charsetStack: seq[Charset]; loader: FileLoader;
     pstream, istream: SocketStream; urandom: PosixStream; cacheId: int) =
-  let factory = newCAtomFactory()
+  initCAtomFactory()
   let confidence = if config.charsetOverride == CHARSET_UNKNOWN:
     ccTentative
   else:
@@ -1925,8 +1924,7 @@ proc launchBuffer*(config: BufferConfig; url: URL; attrs: WindowAttributes;
     url: url,
     charsetStack: charsetStack,
     cacheId: -1,
-    outputId: -1,
-    factory: factory
+    outputId: -1
   )
   buffer.window = newWindow(
     config.scripting,
@@ -1934,7 +1932,6 @@ proc launchBuffer*(config: BufferConfig; url: URL; attrs: WindowAttributes;
     config.styling,
     config.autofocus,
     addr buffer.attrs,
-    factory,
     loader,
     url,
     urandom,
@@ -1961,7 +1958,6 @@ proc launchBuffer*(config: BufferConfig; url: URL; attrs: WindowAttributes;
   buffer.htmlParser = newHTML5ParserWrapper(
     buffer.window,
     buffer.url,
-    buffer.factory,
     confidence,
     buffer.charset
   )
href='#n497'>497 498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592