about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2022-12-26 18:50:20 +0100
committerbptato <nincsnevem662@gmail.com>2022-12-26 18:50:20 +0100
commit636ddbb788a4a4dcd4dc4bc247159b6bb191ef8a (patch)
tree51a262f8455922718b0142e17ab76dee08c042d3 /src
parente694c62b4630a8373ff324b4d3480c067220e480 (diff)
downloadchawan-636ddbb788a4a4dcd4dc4bc247159b6bb191ef8a.tar.gz
DOM: implement many new methods
Diffstat (limited to 'src')
-rw-r--r--src/html/dom.nim129
-rw-r--r--src/html/env.nim3
-rw-r--r--src/js/javascript.nim10
-rw-r--r--src/utils/twtstr.nim56
4 files changed, 187 insertions, 11 deletions
diff --git a/src/html/dom.nim b/src/html/dom.nim
index da08898b..ab800406 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -167,13 +167,17 @@ type
     renderBlockingElements: seq[Element]
 
   CharacterData* = ref object of Node
-    data*: string
-    length*: int
+    data* {.jsget.}: string
 
   Text* = ref object of CharacterData
 
   Comment* = ref object of CharacterData
 
+  CDATASection = ref object of CharacterData
+
+  ProcessingInstruction = ref object of CharacterData
+    target: string
+
   DocumentFragment* = ref object of Node
     host*: Element
 
@@ -604,6 +608,9 @@ func getter[T: int|string](map: NamedNodeMap, i: T): Option[Attr] {.jsgetprop.}
   else:
     return map.getNamedItem(i)
 
+func length(characterData: CharacterData): int {.jsfget.} =
+  return characterData.data.utf16Len
+
 func scriptingEnabled*(element: Element): bool =
   if element.document == nil:
     return false
@@ -771,12 +778,18 @@ func getElementsByTag*(node: Node, tag: TagType): seq[Element] =
   for element in node.elements(tag):
     result.add(element)
 
-func getElementsByTagName(node: Node, tagName: string): HTMLCollection {.jsfunc.} =
+func getElementsByTagName0(root: Node, tagName: string): HTMLCollection =
   if tagName == "*":
-    return newCollection[HTMLCollection](node, func(node: Node): bool = node.isElement, true)
+    return newCollection[HTMLCollection](root, func(node: Node): bool = node.isElement, true)
   let t = tagType(tagName)
   if t != TAG_UNKNOWN:
-    return newCollection[HTMLCollection](node, func(node: Node): bool = node.isElement and Element(node).tagType == t, true)
+    return newCollection[HTMLCollection](root, func(node: Node): bool = node.isElement and Element(node).tagType == t, true)
+
+func getElementsByTagName(document: Document, tagName: string): HTMLCollection {.jsfunc.} =
+  return document.getElementsByTagName0(tagName)
+
+func getElementsByTagName(element: Element, tagName: string): HTMLCollection {.jsfunc.} =
+  return element.getElementsByTagName0(tagName)
 
 func getElementsByClassName(node: Node, classNames: string): HTMLCollection {.jsfunc.} =
   var classes = classNames.split(AsciiWhitespace)
@@ -1039,12 +1052,33 @@ func target*(element: Element): string {.jsfunc.} =
       return base.attr("target")
   return ""
 
+#TODO we shouldn't have to pass document in DOM (so first arg should be window)
 func newText*(document: Document, data: string = ""): Text {.jsctor.} =
   new(result)
   result.nodeType = TEXT_NODE
   result.document = document
   result.data = data
 
+func newCDATASection(document: Document, data: string): CDATASection =
+  new(result)
+  result.nodeType = CDATA_SECTION_NODE
+  result.document = document
+  result.data = data
+
+func newProcessingInstruction(document: Document, target, data: string): ProcessingInstruction =
+  new(result)
+  result.nodeType = PROCESSING_INSTRUCTION_NODE
+  result.document = document
+  result.target = target
+  result.data = data
+
+#TODO ditto
+func newDocumentFragment*(document: Document): DocumentFragment {.jsctor.} =
+  new(result)
+  result.nodeType = DOCUMENT_FRAGMENT_NODE
+  result.document = document
+
+#TODO ditto
 func newComment*(document: Document = nil, data: string = ""): Comment {.jsctor.} =
   new(result)
   result.nodeType = COMMENT_NODE
@@ -1289,12 +1323,20 @@ proc attrigz(element: Element, name: string, value: int) =
   if value > 0:
     element.attr(name, $value)
 
-proc setAttribute(element: Element, qualifiedName, value: string) {.jsfunc.} =
+proc setAttribute(element: Element, qualifiedName, value: string) {.jserr, jsfunc.} =
+  if not qualifiedName.matchNameProduction():
+    #TODO should be DOMException
+    JS_ERR JS_TypeError, "InvalidCharacterError"
+  let qualifiedName = if element.namespace == Namespace.HTML and not element.document.isxml:
+    qualifiedName.toLowerAscii2()
+  else:
+    qualifiedName
   element.attr(qualifiedName, value)
 
 proc setAttributeNS(element: Element, namespace, qualifiedName, value: string) {.jsfunc.} =
   if namespace == "" or namespace == $Namespace.HTML:
     element.attr(qualifiedName, value)
+    return
   if namespace notin NamespaceMap:
     return
   #TODO validate and extract
@@ -1310,13 +1352,38 @@ proc setAttributeNS(element: Element, namespace, qualifiedName, value: string) {
   else:
     element.attributes.attrlist[i].value = value
 
-proc removeAttribute(element: Element, qualifiedName: string) {.jsfunc.} =
+proc removeAttribute(element: Element, qualifiedName: string) {.jserr, jsfunc.} =
+  if not qualifiedName.matchNameProduction():
+    #TODO should be DOMException
+    JS_ERR JS_TypeError, "InvalidCharacterError"
+  let qualifiedName = if element.namespace == Namespace.HTML and not element.document.isxml:
+    qualifiedName.toLowerAscii2()
+  else:
+    qualifiedName
   element.delAttr(qualifiedName)
 
 proc removeAttributeNS(element: Element, namespace, localName: string) {.jsfunc.} =
   #TODO use namespace
   element.delAttr(localName)
 
+proc toggleAttribute(element: Element, qualifiedName: string, force = none(bool)): bool {.jserr, jsfunc.} =
+  if not qualifiedName.matchNameProduction():
+    #TODO should be DOMException
+    JS_ERR JS_TypeError, "InvalidCharacterError"
+  let qualifiedName = if element.namespace == Namespace.HTML and not element.document.isxml:
+    qualifiedName.toLowerAscii2()
+  else:
+    qualifiedName
+  if not element.attrb(qualifiedName):
+    if force.get(true):
+      element.attr(qualifiedName, "")
+      return true
+    return false
+  if not force.get(false):
+    element.delAttr(qualifiedName)
+    return false
+  return true
+
 proc value(attr: Attr, s: string) {.jsfset.} =
   attr.value = s
   if attr.ownerElement != nil:
@@ -1569,6 +1636,8 @@ proc insert*(parent, node, before: Node) =
   if before != nil:
     #TODO live ranges
     discard
+  if parent.nodeType == ELEMENT_NODE:
+    Element(parent).invalid = true
   for node in nodes:
     parent.document.adopt(node)
     if before == nil:
@@ -1853,6 +1922,47 @@ proc prepare*(element: HTMLScriptElement) =
     # parser with a script level <= 1
     element.execute()
 
+#TODO options/custom elements
+proc createElement(document: Document, localName: string): Element {.jserr, jsfunc.} =
+  if not localName.matchNameProduction():
+    #TODO DOMException
+    JS_ERR JS_TypeError, "InvalidCharacterError"
+  let localName = if not document.isxml:
+    localName.toLowerAscii2()
+  else:
+    localName
+  let namespace = if not document.isxml: #TODO or content type is application/xhtml+xml
+    Namespace.HTML
+  else:
+    NO_NAMESPACE
+  return document.newHTMLElement(localName, namespace)
+
+#TODO createElementNS
+
+proc createDocumentFragment(document: Document): DocumentFragment {.jsfunc.} =
+  return newDocumentFragment(document)
+
+proc createTextNode(document: Document, data: string): Text {.jsfunc.} =
+  return newText(document, data)
+
+proc createCDATASection(document: Document, data: string): CDATASection {.jserr, jsfunc.} =
+  if not document.isxml:
+    #TODO should be DOMException
+    JS_ERR JS_TypeError, "NotSupportedError"
+  if "]]>" in data:
+    #TODO should be DOMException
+    JS_ERR JS_TypeError, "InvalidCharacterError"
+  return newCDATASection(document, data)
+
+proc createComment(document: Document, data: string): Comment {.jsfunc.} =
+  return newComment(document, data)
+
+proc createProcessingInstruction(document: Document, target, data: string): ProcessingInstruction {.jsfunc.} =
+  if not target.matchNameProduction() or "?>" in data:
+    #TODO should be DOMException
+    JS_ERR JS_TypeError, "InvalidCharacterError"
+  return newProcessingInstruction(document, target, data)
+
 # Forward definition hack (these are set in selectors.nim)
 var doqsa*: proc (node: Node, q: string): seq[Element]
 var doqs*: proc (node: Node, q: string): Element
@@ -1875,10 +1985,13 @@ proc addDOMModule*(ctx: JSContext) =
   ctx.registerType(Document, parent = nodeCID)
   let characterDataCID = ctx.registerType(CharacterData, parent = nodeCID)
   ctx.registerType(Comment, parent = characterDataCID)
+  ctx.registerType(CDATASection, parent = characterDataCID)
+  ctx.registerType(DocumentFragment, parent = nodeCID)
+  ctx.registerType(ProcessingInstruction, parent = characterDataCID)
   ctx.registerType(Text, parent = characterDataCID)
   ctx.registerType(DocumentType, parent = nodeCID)
   let elementCID = ctx.registerType(Element, parent = nodeCID)
-  ctx.registerType(Attr)
+  ctx.registerType(Attr, parent = nodeCID)
   ctx.registerType(NamedNodeMap)
   let htmlElementCID = ctx.registerType(HTMLElement, parent = elementCID)
   ctx.registerType(HTMLInputElement, parent = htmlElementCID)
diff --git a/src/html/env.nim b/src/html/env.nim
index 7af33d9b..3c1668c7 100644
--- a/src/html/env.nim
+++ b/src/html/env.nim
@@ -48,6 +48,9 @@ proc addNavigatorModule(ctx: JSContext) =
   ctx.registerType(PluginArray)
   ctx.registerType(MimeTypeArray)
 
+#func `$`(window: Window): string {.jsfunc.} =
+#  "[object Window]"
+
 proc newWindow*(scripting: bool, loader = none(FileLoader)): Window =
   result = Window(
     console: console(),
diff --git a/src/js/javascript.nim b/src/js/javascript.nim
index d7ccc115..bf81a09d 100644
--- a/src/js/javascript.nim
+++ b/src/js/javascript.nim
@@ -1240,9 +1240,13 @@ proc rewriteExceptions(gen: var JSFuncGenerator, errors: var seq[string], node:
     if c.kind == nnkCommand and c[0].eqIdent ident("JS_ERR"):
       if gen.copied == nil:
         gen.copied = copy(gen.original)
-      node[i] = quote do:
-        zeroMem(addr result, sizeof(result))
-        return
+      if gen.returnType.isSome:
+        node[i] = quote do:
+          zeroMem(addr result, sizeof(result))
+          return
+      else:
+        node[i] = quote do:
+          return
       if c[1].strVal notin errors:
         errors.add(c[1].strVal)
     elif c.len > 0:
diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim
index 48ea54f9..988f3ad5 100644
--- a/src/utils/twtstr.nim
+++ b/src/utils/twtstr.nim
@@ -670,6 +670,62 @@ func isValidNonZeroInt*(str: string): bool =
     return false
   true
 
+# https://www.w3.org/TR/xml/#NT-Name
+const NameStartCharRanges = [
+  (0xC0, 0xD6),
+  (0xD8, 0xF6),
+  (0xF8, 0x2FF),
+  (0x370, 0x37D),
+  (0x37F, 0x1FFF),
+  (0x200C, 0x200D),
+  (0x2070, 0x218F),
+  (0x2C00, 0x2FEF),
+  (0x3001, 0xD7FF),
+  (0xF900, 0xFDCF),
+  (0xFDF0, 0xFFFD),
+  (0x10000, 0xEFFFF)
+]
+const NameCharRanges = [ # + NameStartCharRanges
+  (0xB7, 0xB7),
+  (0x0300, 0x036F),
+  (0x203F, 0x2040)
+]
+const NameStartCharAscii = {':', '_'} + AsciiAlpha
+const NameCharAscii = NameStartCharAscii + {'-', '.'} + AsciiDigit
+func matchNameProduction*(str: string): bool =
+  if str.len == 0:
+    return true
+  # NameStartChar
+  var i = 0
+  var r: Rune
+  if str[i] in Ascii:
+    if str[i] notin NameStartCharAscii:
+      return false
+    inc i
+  else:
+    fastRuneAt(str, i, r)
+    if binarySearch(NameStartCharRanges, int32(r), (x, y) => cmp(x[0], y)) == -1:
+      return false
+  # NameChar
+  while i < str.len:
+    if str[i] in Ascii:
+      if str[i] notin NameCharAscii:
+        return false
+      inc i
+    else:
+      fastRuneAt(str, i, r)
+      if binarySearch(NameStartCharRanges, int32(r), (x, y) => cmp(x[0], y)) == -1:
+        if binarySearch(NameCharRanges, int32(r), (x, y) => cmp(x[0], y)) == -1:
+          return false
+  return true
+
+func utf16Len*(s: string): int =
+  for r in s.runes:
+    if cast[uint32](r) < 0x10000: # ucs-2
+      result += 1
+    else: # surrogate
+      result += 2
+
 proc expandPath*(path: string): string =
   if path.len == 0:
     return path