about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-02-08 01:32:40 +0100
committerbptato <nincsnevem662@gmail.com>2024-02-08 01:44:32 +0100
commitf666dbafec85ba6ed64db8123ceae28b80c4cb3b (patch)
treec8a26185197f2391551ae9f2570042d7e8e0fa10 /src
parent6faf5cff21f8c1d382ffa605b7aa56be30ff54db (diff)
downloadchawan-f666dbafec85ba6ed64db8123ceae28b80c4cb3b.tar.gz
dom: atomize id, name, DOMTokenList
Diffstat (limited to 'src')
-rw-r--r--src/css/selectorparser.nim40
-rw-r--r--src/css/sheet.nim117
-rw-r--r--src/html/dom.nim108
3 files changed, 162 insertions, 103 deletions
diff --git a/src/css/selectorparser.nim b/src/css/selectorparser.nim
index 9e79a36d..eacb48d9 100644
--- a/src/css/selectorparser.nim
+++ b/src/css/selectorparser.nim
@@ -51,13 +51,17 @@ type
       when defined(debug):
         tags: string
     of ID_SELECTOR:
-      id*: string
+      id*: CAtom
+      when defined(debug):
+        ids: string
+    of CLASS_SELECTOR:
+      class*: CAtom
+      when defined(debug):
+        classs: string
     of ATTR_SELECTOR:
       attr*: string
       value*: string
       rel*: SelectorRelation
-    of CLASS_SELECTOR:
-      class*: string
     of UNIVERSAL_SELECTOR: #TODO namespaces?
       discard
     of PSEUDO_SELECTOR:
@@ -112,9 +116,12 @@ func `$`*(sel: Selector): string =
     when defined(debug):
       return sel.tags
     else:
-      return "tagt " & $int(sel.tag)
+      return "ATOM" & $int(sel.tag)
   of ID_SELECTOR:
-    return '#' & sel.id
+    when defined(debug):
+      return "#" & sel.ids
+    else:
+      return "#ATOM" & $int(sel.id)
   of ATTR_SELECTOR:
     let rel = case sel.rel.t
     of RELATION_EXISTS: ""
@@ -130,7 +137,10 @@ func `$`*(sel: Selector): string =
     of FLAG_S: " s"
     return '[' & sel.attr & rel & sel.value & flag & ']'
   of CLASS_SELECTOR:
-    return '.' & sel.class
+    when defined(debug):
+      return "." & sel.classs
+    else:
+      return ".ATOM" & $int(sel.id)
   of UNIVERSAL_SELECTOR:
     return "*"
   of PSEUDO_SELECTOR:
@@ -397,7 +407,10 @@ proc parseClassSelector(state: var SelectorParser): Selector =
   if not state.has(): fail
   let tok = get_tok state.consume()
   if tok.tokenType != CSS_IDENT_TOKEN: fail
-  return Selector(t: CLASS_SELECTOR, class: tok.value)
+  let class = state.factory.toAtom(tok.value)
+  result = Selector(t: CLASS_SELECTOR, class: class)
+  when defined(debug):
+    result.classs = tok.value
 
 proc parseCompoundSelector(state: var SelectorParser): CompoundSelector =
   result = CompoundSelector()
@@ -408,14 +421,21 @@ proc parseCompoundSelector(state: var SelectorParser): CompoundSelector =
       case tok.tokenType
       of CSS_IDENT_TOKEN:
         inc state.at
-        let tag = state.factory.toAtom(tok.value.toLowerAscii())
-        result.add(Selector(t: TYPE_SELECTOR, tag: tag))
+        let s = tok.value.toLowerAscii()
+        let tag = state.factory.toAtom(s)
+        let sel = Selector(t: TYPE_SELECTOR, tag: tag)
+        when defined(debug):
+          sel.tags = s
+        result.add(sel)
       of CSS_COLON_TOKEN:
         inc state.at
         result.add(state.parsePseudoSelector())
       of CSS_HASH_TOKEN:
         inc state.at
-        result.add(Selector(t: ID_SELECTOR, id: tok.value))
+        let id = state.factory.toAtom(tok.value)
+        result.add(Selector(t: ID_SELECTOR, id: id))
+        when defined(debug):
+          result[^1].ids = tok.value
       of CSS_COMMA_TOKEN: break
       of CSS_DELIM_TOKEN:
         case tok.cvalue
diff --git a/src/css/sheet.nim b/src/css/sheet.nim
index 4990d991..14d9b717 100644
--- a/src/css/sheet.nim
+++ b/src/css/sheet.nim
@@ -25,25 +25,24 @@ type
 
   CSSStylesheet* = ref object
     mqList*: seq[CSSMediaQueryDef]
-    #TODO maybe just array[TagType] would be more efficient
     tagTable: Table[CAtom, seq[CSSRuleDef]]
-    idTable: Table[string, seq[CSSRuleDef]]
-    classTable: Table[string, seq[CSSRuleDef]]
+    idTable: Table[CAtom, seq[CSSRuleDef]]
+    classTable: Table[CAtom, seq[CSSRuleDef]]
     generalList: seq[CSSRuleDef]
     len: int
     factory: CAtomFactory
 
 type SelectorHashes = object
   tag: CAtom
-  id: string
-  class: string
+  id: CAtom
+  class: CAtom
 
 func newStylesheet*(cap: int, factory: CAtomFactory): CSSStylesheet =
   let bucketsize = cap div 2
   return CSSStylesheet(
     tagTable: initTable[CAtom, seq[CSSRuleDef]](bucketsize),
-    idTable: initTable[string, seq[CSSRuleDef]](bucketsize),
-    classTable: initTable[string, seq[CSSRuleDef]](bucketsize),
+    idTable: initTable[CAtom, seq[CSSRuleDef]](bucketsize),
+    classTable: initTable[CAtom, seq[CSSRuleDef]](bucketsize),
     generalList: newSeqOfCap[CSSRuleDef](bucketsize),
     factory: factory
   )
@@ -72,62 +71,66 @@ proc getSelectorIds(hashes: var SelectorHashes, sel: Selector): bool =
   of ATTR_SELECTOR, PSELEM_SELECTOR, UNIVERSAL_SELECTOR:
     return false
   of PSEUDO_SELECTOR:
-    if sel.pseudo.t in {PSEUDO_IS, PSEUDO_WHERE}:
-      # Basically just hash whatever the selectors have in common:
-      #1. get the hashable values of selector 1
-      #2. for every other selector x:
-      #3.   get hashable values of selector x
-      #4.   store hashable values of selector x that aren't stored yet
-      #5.   for every hashable value of selector 1 that doesn't match selector x
-      #6.     cancel hashable value
-      var cancel_tag = false
-      var cancel_id = false
-      var cancel_class = false
-      var i = 0
-      if i < sel.pseudo.fsels.len:
-        hashes.getSelectorIds(sel.pseudo.fsels[i])
-        inc i
-
-      while i < sel.pseudo.fsels.len:
-        var nhashes: SelectorHashes
-        nhashes.getSelectorIds(sel.pseudo.fsels[i])
-        if hashes.tag == CAtomNull:
-          hashes.tag = nhashes.tag
-        elif not cancel_tag and nhashes.tag != CAtomNull and nhashes.tag != hashes.tag:
-          cancel_tag = true
-
-        if hashes.id == "":
-          hashes.id = nhashes.id
-        elif not cancel_id and nhashes.id != "" and nhashes.id != hashes.id:
-          cancel_id = true
-
-        if hashes.class == "":
-          hashes.class = nhashes.class
-        elif not cancel_class and nhashes.class != "" and nhashes.class != hashes.class:
-          cancel_class = true
-
-        inc i
-
-      if cancel_tag:
-        hashes.tag = CAtomNull
-      if cancel_id:
-        hashes.id = ""
-      if cancel_class:
-        hashes.class = ""
-
-      if hashes.tag != CAtomNull or hashes.id != "" or hashes.class != "":
-        return true
+    if sel.pseudo.t notin {PSEUDO_IS, PSEUDO_WHERE}:
+      return false
+    # Basically just hash whatever the selectors have in common:
+    #1. get the hashable values of selector 1
+    #2. for every other selector x:
+    #3.   get hashable values of selector x
+    #4.   store hashable values of selector x that aren't stored yet
+    #5.   for every hashable value of selector 1 that doesn't match selector x
+    #6.     cancel hashable value
+    var cancelTag = false
+    var cancelId = false
+    var cancelClass = false
+    var i = 0
+    if i < sel.pseudo.fsels.len:
+      hashes.getSelectorIds(sel.pseudo.fsels[i])
+      inc i
+
+    while i < sel.pseudo.fsels.len:
+      var nhashes: SelectorHashes
+      nhashes.getSelectorIds(sel.pseudo.fsels[i])
+      if hashes.tag == CAtomNull:
+        hashes.tag = nhashes.tag
+      elif not cancelTag and nhashes.tag != CAtomNull and
+          nhashes.tag != hashes.tag:
+        cancelTag = true
+
+      if hashes.id == CAtomNull:
+        hashes.id = nhashes.id
+      elif not cancelId and nhashes.id != CAtomNull and
+          nhashes.id != hashes.id:
+        cancelId = true
+
+      if hashes.class == CAtomNull:
+        hashes.class = nhashes.class
+      elif not cancelClass and nhashes.class != CAtomNull and
+          nhashes.class != hashes.class:
+        cancelClass = true
+
+      inc i
+
+    if cancelTag:
+      hashes.tag = CAtomNull
+    if cancelId:
+      hashes.id = CAtomNull
+    if cancelClass:
+      hashes.class = CAtomNull
+
+    return hashes.tag != CAtomNull or hashes.id != CAtomNull or
+      hashes.class != CAtomNull
 
 proc ruleDefCmp(a, b: CSSRuleDef): int =
   cmp(a.idx, b.idx)
 
-iterator genRules*(sheet: CSSStylesheet, tag: CAtom, id: string,
-    classes: seq[string]): CSSRuleDef =
+iterator genRules*(sheet: CSSStylesheet, tag, id: CAtom, classes: seq[CAtom]):
+    CSSRuleDef =
   var rules: seq[CSSRuleDef]
   sheet.tagTable.withValue(tag, v):
     for rule in v[]:
       rules.add(rule)
-  if id != "":
+  if id != CAtomNull:
     sheet.idTable.withValue(id, v):
       for rule in v[]:
         rules.add(rule)
@@ -150,12 +153,12 @@ proc add(sheet: var CSSStylesheet, rule: CSSRuleDef) =
         p[].add(rule)
       do:
         sheet.tagTable[hashes.tag] = @[rule]
-    elif hashes.id != "":
+    elif hashes.id != CAtomNull:
       sheet.idTable.withValue(hashes.id, p):
         p[].add(rule)
       do:
         sheet.idTable[hashes.id] = @[rule]
-    elif hashes.class != "":
+    elif hashes.class != CAtomNull:
       sheet.classTable.withValue(hashes.class, p):
         p[].add(rule)
       do:
diff --git a/src/html/dom.nim b/src/html/dom.nim
index cb4d71ee..700d1d22 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -107,7 +107,7 @@ type
   HTMLAllCollection = ref object of Collection
 
   DOMTokenList = ref object
-    toks*: seq[string]
+    toks*: seq[CAtom]
     element: Element
     localName: CAtom
 
@@ -212,7 +212,8 @@ type
     prefix*: string
     localName*: CAtom
 
-    id* {.jsget.}: string
+    id*: CAtom
+    name*: CAtom
     classList* {.jsget.}: DOMTokenList
     attrs: seq[AttrData]
     attributesInternal: NamedNodeMap
@@ -277,7 +278,6 @@ type
     relList {.jsget.}: DOMTokenList
 
   HTMLFormElement* = ref object of HTMLElement
-    name*: string
     smethod*: string
     enctype*: string
     novalidate*: bool
@@ -1134,16 +1134,29 @@ func length(tokenList: DOMTokenList): uint32 {.jsfget.} =
 
 func item(tokenList: DOMTokenList, i: int): Option[string] {.jsfunc.} =
   if i < tokenList.toks.len:
-    return some(tokenList.toks[i])
+    return some(tokenList.element.document.toStr(tokenList.toks[i]))
+  return none(string)
+
+func contains*(tokenList: DOMTokenList, a: CAtom): bool =
+  return a in tokenList.toks
 
-func contains*(tokenList: DOMTokenList, s: string): bool {.jsfunc.} =
-  return s in tokenList.toks
+func jsContains(tokenList: DOMTokenList, s: string): bool
+    {.jsfunc: "contains".} =
+  return tokenList.element.document.toAtom(s) in tokenList
+
+func `$`(tokenList: DOMTokenList): string {.jsfunc.} =
+  var s = ""
+  for i, tok in tokenList.toks:
+    if i != 0:
+      s &= ' '
+    s &= tokenList.element.document.toStr(tok)
+  return s
 
 proc update(tokenList: DOMTokenList) =
   if not tokenList.element.attrb(tokenList.localName) and
       tokenList.toks.len == 0:
     return
-  tokenList.element.attr(tokenList.localName, tokenList.toks.join(' '))
+  tokenList.element.attr(tokenList.localName, $tokenList)
 
 func validateDOMToken(tok: string): Err[DOMException] =
   if tok == "":
@@ -1151,12 +1164,14 @@ func validateDOMToken(tok: string): Err[DOMException] =
   if AsciiWhitespace in tok:
     return errDOMException("Got a string containing whitespace",
       "InvalidCharacterError")
+  return ok()
 
 proc add(tokenList: DOMTokenList, tokens: varargs[string]): Err[DOMException]
     {.jsfunc.} =
   for tok in tokens:
     ?validateDOMToken(tok)
   for tok in tokens:
+    let tok = tokenList.element.document.toAtom(tok)
     tokenList.toks.add(tok)
   tokenList.update()
   return ok()
@@ -1166,6 +1181,7 @@ proc remove(tokenList: DOMTokenList, tokens: varargs[string]):
   for tok in tokens:
     ?validateDOMToken(tok)
   for tok in tokens:
+    let tok = tokenList.element.document.toAtom(tok)
     let i = tokenList.toks.find(tok)
     if i != -1:
       tokenList.toks.delete(i)
@@ -1175,6 +1191,7 @@ proc remove(tokenList: DOMTokenList, tokens: varargs[string]):
 proc toggle(tokenList: DOMTokenList, token: string, force = none(bool)):
     DOMResult[bool] {.jsfunc.} =
   ?validateDOMToken(token)
+  let token = tokenList.element.document.toAtom(token)
   let i = tokenList.toks.find(token)
   if i != -1:
     if not force.get(false):
@@ -1192,9 +1209,11 @@ proc replace(tokenList: DOMTokenList, token, newToken: string):
     DOMResult[bool] {.jsfunc.} =
   ?validateDOMToken(token)
   ?validateDOMToken(newToken)
+  let token = tokenList.element.document.toAtom(token)
   let i = tokenList.toks.find(token)
   if i == -1:
     return ok(false)
+  let newToken = tokenList.element.document.toAtom(newToken)
   tokenList.toks[i] = newToken
   tokenList.update()
   return ok(true)
@@ -1217,9 +1236,6 @@ func supports(tokenList: DOMTokenList, token: string):
   return err(newTypeError("No supported tokens defined for attribute " &
     localName))
 
-func `$`(tokenList: DOMTokenList): string {.jsfunc.} =
-  return tokenList.toks.join(' ')
-
 func value(tokenList: DOMTokenList): string {.jsfget.} =
   return $tokenList
 
@@ -1314,12 +1330,15 @@ func hasprop(collection: HTMLCollection, u: uint32): bool {.jshasprop.} =
 func item(collection: HTMLCollection, u: uint32): Element {.jsfunc.} =
   if u < collection.length:
     return Element(collection.snapshot[int(u)])
+  return nil
 
 func namedItem(collection: HTMLCollection, s: string): Element {.jsfunc.} =
+  let a = collection.root.document.toAtom(s)
   for it in collection.snapshot:
     let it = Element(it)
-    if it.id == s or it.namespace == Namespace.HTML and it.attr("name") == s:
+    if it.id == a or it.namespace == Namespace.HTML and it.name == a:
       return it
+  return nil
 
 func getter[T: uint32|string](collection: HTMLCollection, u: T):
     Option[Element] {.jsgetprop.} =
@@ -1330,20 +1349,18 @@ func getter[T: uint32|string](collection: HTMLCollection, u: T):
 
 func names(ctx: JSContext, collection: HTMLCollection): JSPropertyEnumList
     {.jspropnames.} =
-  let aName = collection.root.document.toAtom("name") #TODO enumize
   let L = collection.length
   var list = newJSPropertyEnumList(ctx, L)
-  var ids: OrderedSet[string]
+  var ids: OrderedSet[CAtom]
   for u in 0 ..< L:
     list.add(u)
     let elem = collection.item(u)
-    if elem.id != "":
+    if elem.id != CAtomNull:
       ids.incl(elem.id)
     if elem.namespace == Namespace.HTML:
-      let name = elem.attr(aName)
-      ids.incl(name)
+      ids.incl(elem.name)
   for id in ids:
-    list.add(id)
+    list.add(collection.root.document.toStr(id))
   return list
 
 # HTMLAllCollection
@@ -1882,6 +1899,7 @@ func findAncestor*(node: Node, tagTypes: set[TagType]): Element =
 func getElementById(node: Node, id: string): Element {.jsfunc.} =
   if id.len == 0:
     return nil
+  let id = node.document.toAtom(id)
   for child in node.elements:
     if child.id == id:
       return child
@@ -1917,28 +1935,35 @@ func getElementsByTagName(element: Element, tagName: string): HTMLCollection {.j
   return element.getElementsByTagName0(tagName)
 
 func getElementsByClassName0(node: Node, classNames: string): HTMLCollection =
-  var classes = classNames.split(AsciiWhitespace)
-  let isquirks = node.document.mode == QUIRKS
+  var classAtoms = newSeq[CAtom]()
+  let document = node.document
+  let isquirks = document.mode == QUIRKS
   if isquirks:
-    for class in classes.mitems:
-      for c in class.mitems:
-        c = c.toLowerAscii()
+    for class in classNames.split(AsciiWhitespace):
+      classAtoms.add(document.toAtom(class.toLowerAscii()))
+  else:
+    for class in classNames.split(AsciiWhitespace):
+      classAtoms.add(document.toAtom(class))
   return newCollection[HTMLCollection](node,
     func(node: Node): bool =
       if node of Element:
+        let element = Element(node)
         if isquirks:
-          var cl = Element(node).classList
-          for tok in cl.toks.mitems:
-            for c in tok.mitems:
-              c = c.toLowerAscii()
-          for class in classes:
+          var cl = newSeq[CAtom]()
+          for tok in element.classList.toks:
+            let s = document.toStr(tok)
+            cl.add(document.toAtom(s.toLowerAscii()))
+          for class in classAtoms:
             if class notin cl:
               return false
         else:
-          for class in classes:
-            if class notin Element(node).classList:
+          for class in classAtoms:
+            if class notin element.classList.toks:
               return false
-        return true, true, false)
+        return true,
+    islive = true,
+    childonly = false
+  )
 
 func getElementsByClassName(document: Document, classNames: string): HTMLCollection {.jsfunc.} =
   return document.getElementsByClassName0(classNames)
@@ -2060,6 +2085,9 @@ func serializeFragment(node: Node): string =
   return s
 
 # Element attribute reflection (getters)
+func jsId(element: Element): string {.jsfget: "id".} =
+  return element.document.toStr(element.id)
+
 func innerHTML(element: Element): string {.jsfget.} =
   #TODO xml
   return element.serializeFragment()
@@ -2211,11 +2239,13 @@ func formmethod*(element: Element): FormMethod =
 func findAnchor*(document: Document, id: string): Element =
   if id.len == 0:
     return nil
+  let id = document.toAtom(id)
   for child in document.elements:
     if child.id == id:
       return child
-    if child of HTMLAnchorElement and child.attr("name") == id:
+    if child of HTMLAnchorElement and child.name == id:
       return child
+  return nil
 
 # Forward declaration hack
 isDefaultPassive = func (eventTarget: EventTarget): bool =
@@ -2672,10 +2702,17 @@ proc reflectAttrs(element: Element, name: CAtom, value: string) =
     if name == n:
       element.val.toks.setLen(0)
       for x in value.split(AsciiWhitespace):
-        if x != "" and x notin element.val:
-          element.val.toks.add(x)
+        if x != "":
+          let a = element.document.toAtom(x)
+          if a notin element.val:
+            element.val.toks.add(a)
       return
-  element.reflect_str "id", id
+  if name == "id":
+    element.id = element.document.toAtom(value)
+    return
+  if name == "name":
+    element.name = element.document.toAtom(value)
+    return
   element.reflect_domtoklist "class", classList
   #TODO internalNonce
   if name == "style":
@@ -2880,8 +2917,7 @@ proc removeNamedItemNS(map: NamedNodeMap, namespace, localName: string):
     return ok(attr)
   return errDOMException("Item not found", "NotFoundError")
 
-proc id(element: Element, id: string) {.jsfset.} =
-  element.id = id
+proc jsId(element: Element, id: string) {.jsfset: "id".} =
   element.attr("id", id)
 
 # Pass an index to avoid searching for the node in parent's child list.