about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2025-02-13 19:34:01 +0100
committerbptato <nincsnevem662@gmail.com>2025-02-13 19:34:01 +0100
commit7ad94b17c94ccd9e63609ffd4ecde7b82f728eaa (patch)
treed30559fbb208282745cddc4d3cf2e12810b9d0eb /src
parent8b841c16743b88802f1ff161b96d1908fd46964c (diff)
downloadchawan-7ad94b17c94ccd9e63609ffd4ecde7b82f728eaa.tar.gz
catom: make factory global
This isn't great, but neither was passing around a pointer that pointed
to a single object.
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
   )