about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-08-11 14:05:15 +0200
committerbptato <nincsnevem662@gmail.com>2024-08-11 14:05:15 +0200
commita0c6f80f76d60bc2bb36942d4162b90e6b5aa2a2 (patch)
treedbf93dc3b168c4a9e135f0fa4e1300892645ba68
parentd0910f85adfec4a2cddfe2297be821841f4dd671 (diff)
downloadchawan-a0c6f80f76d60bc2bb36942d4162b90e6b5aa2a2.tar.gz
dom: remove generic JS bindings
Dispatch manually with fromJS instead.
-rw-r--r--src/config/mimetypes.nim4
-rw-r--r--src/html/dom.nim149
-rw-r--r--src/html/formdata.nim31
-rw-r--r--src/local/client.nim2
-rw-r--r--src/local/container.nim38
-rw-r--r--src/local/pager.nim17
-rw-r--r--src/server/buffer.nim11
-rw-r--r--src/types/url.nim36
-rw-r--r--src/utils/mimeguess.nim3
9 files changed, 156 insertions, 135 deletions
diff --git a/src/config/mimetypes.nim b/src/config/mimetypes.nim
index 9beb42c7..d9e21aaf 100644
--- a/src/config/mimetypes.nim
+++ b/src/config/mimetypes.nim
@@ -6,8 +6,8 @@ import utils/twtstr
 # extension -> type
 type MimeTypes* = distinct Table[string, string]
 
-template `[]`*(mimeTypes: MimeTypes; k: string): string =
-  Table[string, string](mimeTypes)[k]
+template getOrDefault*(mimeTypes: MimeTypes; k, fallback: string): string =
+  Table[string, string](mimeTypes).getOrDefault(k, fallback)
 
 template contains*(mimeTypes: MimeTypes; k: string): bool =
   k in Table[string, string](mimeTypes)
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 20f21264..208f8168 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -1302,40 +1302,41 @@ proc update(tokenList: DOMTokenList) =
     return
   tokenList.element.attr(tokenList.localName, $tokenList)
 
-func validateDOMToken(tok: string): Err[DOMException] =
-  if tok == "":
+func validateDOMToken(ctx: JSContext; document: Document; tok: JSValue):
+    DOMResult[CAtom] =
+  var res: string
+  ?ctx.fromJS(tok, res)
+  if res == "":
     return errDOMException("Got an empty string", "SyntaxError")
-  if AsciiWhitespace in tok:
+  if AsciiWhitespace in res:
     return errDOMException("Got a string containing whitespace",
       "InvalidCharacterError")
-  return ok()
+  return ok(document.toAtom(res))
 
-proc add(tokenList: DOMTokenList; tokens: varargs[string]): Err[DOMException]
-    {.jsfunc.} =
-  for tok in tokens:
-    ?validateDOMToken(tok)
+proc add(ctx: JSContext; tokenList: DOMTokenList; tokens: varargs[JSValue]):
+    Err[DOMException] {.jsfunc.} =
+  var toks: seq[CAtom] = @[]
   for tok in tokens:
-    let tok = tokenList.element.document.toAtom(tok)
-    tokenList.toks.add(tok)
+    toks.add(?ctx.validateDOMToken(tokenList.element.document, tok))
+  tokenList.toks.add(toks)
   tokenList.update()
   return ok()
 
-proc remove(tokenList: DOMTokenList; tokens: varargs[string]):
+proc remove(ctx: JSContext; tokenList: DOMTokenList; tokens: varargs[JSValue]):
     Err[DOMException] {.jsfunc.} =
+  var toks: seq[CAtom] = @[]
   for tok in tokens:
-    ?validateDOMToken(tok)
-  for tok in tokens:
-    let tok = tokenList.element.document.toAtom(tok)
+    toks.add(?ctx.validateDOMToken(tokenList.element.document, tok))
+  for tok in toks:
     let i = tokenList.toks.find(tok)
     if i != -1:
       tokenList.toks.delete(i)
   tokenList.update()
   return ok()
 
-proc toggle(tokenList: DOMTokenList; token: string; force = none(bool)):
-    DOMResult[bool] {.jsfunc.} =
-  ?validateDOMToken(token)
-  let token = tokenList.element.document.toAtom(token)
+proc toggle(ctx: JSContext; tokenList: DOMTokenList; token: JSValue;
+    force = none(bool)): DOMResult[bool] {.jsfunc.} =
+  let token = ?ctx.validateDOMToken(tokenList.element.document, token)
   let i = tokenList.toks.find(token)
   if i != -1:
     if not force.get(false):
@@ -1349,15 +1350,13 @@ proc toggle(tokenList: DOMTokenList; token: string; force = none(bool)):
     return ok(true)
   return ok(false)
 
-proc replace(tokenList: DOMTokenList; token, newToken: string):
+proc replace(ctx: JSContext; tokenList: DOMTokenList; token, newToken: JSValue):
     DOMResult[bool] {.jsfunc.} =
-  ?validateDOMToken(token)
-  ?validateDOMToken(newToken)
-  let token = tokenList.element.document.toAtom(token)
+  let token = ?ctx.validateDOMToken(tokenList.element.document, token)
+  let newToken = ?ctx.validateDOMToken(tokenList.element.document, newToken)
   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)
@@ -1520,6 +1519,7 @@ func item(collection: HTMLAllCollection; i: uint32): Element {.jsfunc.} =
   let i = int(i)
   if i < collection.len:
     return Element(collection.snapshot[i])
+  return nil
 
 func getter(collection: HTMLAllCollection; i: uint32): Option[Element]
     {.jsgetprop.} =
@@ -1780,40 +1780,43 @@ func getAttributeNS(element: Element; namespace, localName: string):
     return some(element.attrs[i].value)
   return none(string)
 
-proc getNamedItem(map: NamedNodeMap; qualifiedName: string): Option[Attr]
+proc getNamedItem(map: NamedNodeMap; qualifiedName: string): Attr
     {.jsfunc.} =
   let i = map.element.findAttr(qualifiedName)
   if i != -1:
-    return some(map.getAttr(i))
-  return none(Attr)
+    return map.getAttr(i)
+  return nil
 
 proc getNamedItemNS(map: NamedNodeMap; namespace, localName: string):
-    Option[Attr] {.jsfunc.} =
+    Attr {.jsfunc.} =
   let i = map.element.findAttrNS(namespace, localName)
   if i != -1:
-    return some(map.getAttr(i))
-  return none(Attr)
+    return map.getAttr(i)
+  return nil
 
 func length(map: NamedNodeMap): uint32 {.jsfget.} =
   return uint32(map.element.attrs.len)
 
-proc item(map: NamedNodeMap; i: uint32): Option[Attr] {.jsfunc.} =
+proc item(map: NamedNodeMap; i: uint32): Attr {.jsfunc.} =
   if int(i) < map.element.attrs.len:
-    return some(map.getAttr(int(i)))
-  return none(Attr)
-
-func hasprop[T: uint32|string](map: NamedNodeMap; i: T): bool {.jshasprop.} =
-  when T is uint32:
-    return int(i) < map.element.attrs.len
-  else:
-    return map.getNamedItem(i).isSome
+    return map.getAttr(int(i))
+  return nil
 
-func getter[T: uint32|string](map: NamedNodeMap; i: T): Option[Attr]
+func getter(ctx: JSContext; map: NamedNodeMap; atom: JSAtom): Opt[Attr]
     {.jsgetprop.} =
-  when T is uint32:
-    return map.item(i)
-  else:
-    return map.getNamedItem(i)
+  var u: uint32
+  if ctx.fromJS(atom, u).isSome:
+    return ok(map.item(u))
+  var s: string
+  ?ctx.fromJS(atom, s)
+  return ok(map.getNamedItem(s))
+
+func hasprop(ctx: JSContext; map: NamedNodeMap; atom: JSAtom): cint
+    {.jshasprop.} =
+  var x = ctx.getter(map, atom)
+  if x.isNone:
+    return -1
+  return cint(x.get != nil)
 
 func names(ctx: JSContext; map: NamedNodeMap): JSPropertyEnumList
     {.jspropnames.} =
@@ -1912,8 +1915,8 @@ template toOA*(writeBuffer: DocumentWriteBuffer): openArray[char] =
 proc CDB_parseDocumentWriteChunk(wrapper: pointer) {.importc.}
 
 # https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#document-write-steps
-proc write(document: Document; text: varargs[string]): Err[DOMException]
-    {.jsfunc.} =
+proc write(ctx: JSContext; document: Document; args: varargs[JSValue]):
+    Err[DOMException] {.jsfunc.} =
   if document.isxml:
     return errDOMException("document.write not supported in XML documents",
       "InvalidStateError")
@@ -1927,8 +1930,12 @@ proc write(document: Document; text: varargs[string]): Err[DOMException]
   if document.writeBuffers.len == 0:
     return ok() #TODO (probably covered by open above)
   let buffer = document.writeBuffers[^1]
-  for s in text:
-    buffer.data &= s
+  var text = ""
+  for arg in args:
+    var s: string
+    ?ctx.fromJS(arg, s)
+    text &= s
+  buffer.data &= text
   if document.parserBlockingScript == nil:
     CDB_parseDocumentWriteChunk(document.parser)
   return ok()
@@ -2917,17 +2924,20 @@ func IDLAttributeToCSSProperty(s: string; dashPrefix = false): string =
     else:
       result &= c
 
-proc getter[T: uint32|string](this: CSSStyleDeclaration; u: T):
-    Option[string] {.jsgetprop.} =
-  when T is uint32:
-    return this.item(u)
-  else:
-    if u.isSupportedProperty():
-      return some(this.getPropertyValue(u))
-    let u = IDLAttributeToCSSProperty(u)
-    if u.isSupportedProperty():
-      return some(this.getPropertyValue(u))
-    return none(string)
+proc getter(ctx: JSContext; this: CSSStyleDeclaration; atom: JSAtom):
+    JSValue {.jsgetprop.} =
+  var u: uint32
+  if ctx.fromJS(atom, u).isSome:
+    return ctx.toJS(this.item(u))
+  var s: string
+  if ctx.fromJS(atom, s).isNone:
+    return JS_EXCEPTION
+  if s.isSupportedProperty():
+    return ctx.toJS(this.getPropertyValue(s))
+  s = IDLAttributeToCSSProperty(s)
+  if s.isSupportedProperty():
+    return ctx.toJS(this.getPropertyValue(s))
+  return JS_NULL #TODO eh?
 
 proc setValue(this: CSSStyleDeclaration; i: int; cvals: seq[CSSComponentValue]):
     Err[void] =
@@ -2938,23 +2948,28 @@ proc setValue(this: CSSStyleDeclaration; i: int; cvals: seq[CSSComponentValue]):
   this.decls[i].value = cvals
   return ok()
 
-proc setter[T: uint32|string](this: CSSStyleDeclaration; u: T;
-    value: string) {.jssetprop.} =
+proc setter(ctx: JSContext; this: CSSStyleDeclaration; atom: JSAtom;
+    value: string): Opt[void] {.jssetprop.} =
   let cvals = parseComponentValues(value)
-  when u is uint32:
+  var u: uint32
+  if ctx.fromJS(atom, u).isSome:
     if this.setValue(int(u), cvals).isNone:
-      return
+      return ok()
   else:
-    if (let i = this.find(u); i != -1):
+    var s: string
+    ?ctx.fromJS(atom, s)
+    if (let i = this.find(s); i != -1):
       if this.setValue(i, cvals).isNone:
-        return
+        # not err! this does not throw.
+        return ok()
     else:
       var dummy: seq[CSSComputedEntry]
-      let val0 = parseComputedValues(dummy, u, cvals)
+      let val0 = parseComputedValues(dummy, s, cvals)
       if val0.isNone:
-        return
-      this.decls.add(CSSDeclaration(name: u, value: cvals))
+        return ok()
+      this.decls.add(CSSDeclaration(name: s, value: cvals))
   this.element.attr(satStyle, $this.decls)
+  ok()
 
 proc style*(element: Element): CSSStyleDeclaration {.jsfget.} =
   if element.cachedStyle == nil:
diff --git a/src/html/formdata.nim b/src/html/formdata.nim
index 687c5600..b5a21b1e 100644
--- a/src/html/formdata.nim
+++ b/src/html/formdata.nim
@@ -5,6 +5,7 @@ import html/enums
 import io/dynstream
 import js/base64
 import js/domexception
+import monoucha/fromjs
 import monoucha/javascript
 import monoucha/tojs
 import types/blob
@@ -26,7 +27,10 @@ proc generateBoundary(): string =
 proc newFormData0*(): FormData =
   return FormData(boundary: generateBoundary())
 
-proc newFormData*(form: HTMLFormElement = nil; submitter: HTMLElement = nil):
+proc newFormData0*(entries: seq[FormDataEntry]): FormData =
+  return FormData(boundary: generateBoundary(), entries: entries)
+
+proc newFormData(form: HTMLFormElement = nil; submitter: HTMLElement = nil):
     DOMResult[FormData] {.jsctor.} =
   let this = newFormData0()
   if form != nil:
@@ -43,27 +47,28 @@ proc newFormData*(form: HTMLFormElement = nil; submitter: HTMLElement = nil):
 
 #TODO filename should not be allowed for string entries
 # in other words, this should be an overloaded function, not just an or type
-proc append*[T: string|Blob](this: FormData; name: string; value: T;
-    filename = none(string)) {.jsfunc.} =
-  when T is Blob:
+proc append*(ctx: JSContext; this: FormData; name: string; val: JSValue;
+    filename = none(string)): Opt[void] {.jsfunc.} =
+  var blob: Blob
+  if ctx.fromJS(val, blob).isSome:
     let filename = if filename.isSome:
       filename.get
-    elif value of WebFile:
-      WebFile(value).name
+    elif blob of WebFile:
+      WebFile(blob).name
     else:
       "blob"
     this.entries.add(FormDataEntry(
       name: name,
       isstr: false,
-      value: value,
+      value: blob,
       filename: filename
     ))
-  else: # string
-    this.entries.add(FormDataEntry(
-      name: name,
-      isstr: true,
-      svalue: value
-    ))
+    ok()
+  else:
+    var s: string
+    ?ctx.fromJS(val, s)
+    this.entries.add(FormDataEntry(name: name, isstr: true, svalue: s))
+    ok()
 
 proc delete(this: FormData; name: string) {.jsfunc.} =
   for i in countdown(this.entries.high, 0):
diff --git a/src/local/client.nim b/src/local/client.nim
index d3fd9ebe..3d3e1bb0 100644
--- a/src/local/client.nim
+++ b/src/local/client.nim
@@ -821,7 +821,7 @@ proc newClient*(config: Config; forkserver: ForkServer; loaderPid: int;
     factory: newCAtomFactory(),
     loader: loader
   )
-  jsrt.setInterruptHandler(interruptHandler, cast[pointer](client))
+  JS_SetInterruptHandler(jsrt, interruptHandler, cast[pointer](client))
   let global = JS_GetGlobalObject(jsctx)
   jsctx.setGlobal(client)
   jsctx.definePropertyE(global, "cmd", config.cmd.jsObj)
diff --git a/src/local/container.nim b/src/local/container.nim
index de4a78ff..a01d5e3b 100644
--- a/src/local/container.nim
+++ b/src/local/container.nim
@@ -1340,26 +1340,26 @@ proc updateCursor(container: Container) =
     container.setCursorY(container.lastVisibleLine)
     container.alert("Last line is #" & $container.numLines)
 
-proc gotoLine*[T: string|int](container: Container; s: T) =
-  when s is string:
-    if s == "":
-      container.redraw = true
-    elif s[0] == '^':
-      container.cursorFirstLine()
-    elif s[0] == '$':
-      container.cursorLastLine()
-    else:
-      let i = parseUInt32(s, allowSign = true)
-      if i.isSome and i.get > 0:
-        container.markPos0()
-        container.setCursorY(int(i.get - 1))
-        container.markPos()
-      else:
-        container.alert("First line is #1") # :)
+proc gotoLine*(container: Container; s: string) =
+  if s == "":
+    container.redraw = true
+  elif s[0] == '^':
+    container.cursorFirstLine()
+  elif s[0] == '$':
+    container.cursorLastLine()
   else:
-    container.markPos0()
-    container.setCursorY(s - 1)
-    container.markPos()
+    let i = parseUInt32(s, allowSign = true)
+    if i.isSome and i.get > 0:
+      container.markPos0()
+      container.setCursorY(int(i.get - 1))
+      container.markPos()
+    else:
+      container.alert("First line is #1") # :)
+
+proc gotoLine*(container: Container; n: int) =
+  container.markPos0()
+  container.setCursorY(n - 1)
+  container.markPos()
 
 proc pushCursorPos*(container: Container) =
   if container.select != nil:
diff --git a/src/local/pager.nim b/src/local/pager.nim
index 592db0c6..354b1dc5 100644
--- a/src/local/pager.nim
+++ b/src/local/pager.nim
@@ -312,12 +312,17 @@ proc isearchBackward(pager: Pager) {.jsfunc.} =
   pager.container.markPos0()
   pager.setLineEdit(lmISearchB)
 
-proc gotoLine[T: string|int](pager: Pager; s: T = "") {.jsfunc.} =
-  when s is string:
-    if s == "":
-      pager.setLineEdit(lmGotoLine)
-      return
-  pager.container.gotoLine(s)
+proc gotoLine(ctx: JSContext; pager: Pager; val = JS_UNDEFINED): Opt[void]
+    {.jsfunc.} =
+  var n: int
+  if ctx.fromJS(val, n).isSome:
+    pager.container.gotoLine(n)
+  elif JS_IsUndefined(val):
+    pager.setLineEdit(lmGotoLine)
+  else:
+    var s: string
+    ?ctx.fromJS(val, s)
+    pager.container.gotoLine(s)
 
 proc dumpAlerts*(pager: Pager) =
   for msg in pager.alerts:
diff --git a/src/server/buffer.nim b/src/server/buffer.nim
index 7abac9e1..f92d8174 100644
--- a/src/server/buffer.nim
+++ b/src/server/buffer.nim
@@ -1194,14 +1194,9 @@ proc cancel*(buffer: Buffer) {.proxy.} =
 
 #https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart/form-data-encoding-algorithm
 proc serializeMultipart(entries: seq[FormDataEntry]): FormData =
-  let formData = newFormData0()
-  for entry in entries:
-    let name = makeCRLF(entry.name)
-    if entry.isstr:
-      let value = makeCRLF(entry.svalue)
-      formData.append(name, value)
-    else:
-      formData.append(name, entry.value, some(entry.filename))
+  let formData = newFormData0(entries)
+  for entry in formData.entries.mitems:
+    entry.name = makeCRLF(entry.name)
   return formData
 
 proc serializePlainTextFormData(kvs: seq[(string, string)]): string =
diff --git a/src/types/url.nim b/src/types/url.nim
index 7ecc9531..deeea08b 100644
--- a/src/types/url.nim
+++ b/src/types/url.nim
@@ -6,9 +6,11 @@ import std/tables
 import std/unicode
 
 import lib/punycode
+import monoucha/fromjs
 import monoucha/javascript
 import monoucha/jserror
 import monoucha/libunicode
+import monoucha/quickjs
 import types/blob
 import types/opt
 import utils/luwrap
@@ -1103,27 +1105,27 @@ proc serializeFormURLEncoded*(kvs: seq[(string, string)]; spaceAsPlus = true):
     result &= '='
     result.percentEncode(value, ApplicationXWWWFormUrlEncodedSet, spaceAsPlus)
 
-proc newURLSearchParams[
-      T: seq[(string, string)]|
-      Table[string, string]|
-      string
-    ](init: T = ""): URLSearchParams {.jsctor.} =
-  result = URLSearchParams()
-  when T is seq[(string, string)]:
-    result.list = init
-  elif T is Table[string, string]:
-    for k, v in init:
-      result.list.add((k, v))
-  elif T is string:
-    let init = if init.len > 0 and init[0] == '?':
-      init.substr(1)
+proc newURLSearchParams(ctx: JSContext; init: varargs[JSValue]):
+    Opt[URLSearchParams] {.jsctor.} =
+  let params = URLSearchParams()
+  if init.len > 0:
+    let val = init[0]
+    if ctx.fromJS(val, params.list).isSome:
+      discard
+    elif (var t: Table[string, string]; ctx.fromJS(val, t).isSome):
+      for k, v in t:
+        params.list.add((k, v))
     else:
-      init
-    result.list = parseFromURLEncoded(init)
+      var res: string
+      ?ctx.fromJS(val, res)
+      if res.len > 0 and res[0] == '?':
+        res.delete(0..0)
+      params.list = parseFromURLEncoded(res)
+  return ok(params)
 
 proc searchParams(url: URL): URLSearchParams {.jsfget.} =
   if url.searchParamsInternal == nil:
-    let params = newURLSearchParams(url.query.get(""))
+    let params = URLSearchParams(list: parseFromURLEncoded(url.query.get("")))
     params.url = url
     url.searchParamsInternal = params
   return url.searchParamsInternal
diff --git a/src/utils/mimeguess.nim b/src/utils/mimeguess.nim
index f13eaf89..d6224303 100644
--- a/src/utils/mimeguess.nim
+++ b/src/utils/mimeguess.nim
@@ -19,8 +19,7 @@ func guessContentType*(mimeTypes: MimeTypes; path: string;
       break
   if n > 0:
     let ext = path.substr(n + 1)
-    if ext in mimeTypes:
-      return mimeTypes[ext]
+    return mimeTypes.getOrDefault(ext, fallback)
   return fallback
 
 const JavaScriptTypes = [