about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bindings/quickjs.nim6
-rw-r--r--src/config/config.nim11
-rw-r--r--src/css/cascade.nim8
-rw-r--r--src/html/dom.nim128
-rw-r--r--src/js/javascript.nim53
-rw-r--r--src/js/propertyenumlist.nim42
6 files changed, 213 insertions, 35 deletions
diff --git a/src/bindings/quickjs.nim b/src/bindings/quickjs.nim
index f6719e3d..7f27876b 100644
--- a/src/bindings/quickjs.nim
+++ b/src/bindings/quickjs.nim
@@ -102,7 +102,7 @@ type
     get_own_property*: proc (ctx: JSContext, desc: ptr JSPropertyDescriptor,
                              obj: JSValue, prop: JSAtom): cint {.cdecl.}
     get_own_property_names*: proc (ctx: JSContext,
-                                   ptab: ptr ptr JSPropertyEnum,
+                                   ptab: ptr ptr UncheckedArray[JSPropertyEnum],
                                    plen: ptr uint32, obj: JSValue): cint {.cdecl.}
     delete_property*: proc (ctx: JSContext, obj: JSValue, prop: JSAtom): cint {.cdecl.}
     define_own_property*: proc (ctx: JSContext, this_obj: JSValue,
@@ -381,6 +381,7 @@ proc JS_NewBigUInt64*(ctx: JSContext, val: uint64): JSValue
 proc JS_NewFloat64*(ctx: JSContext, val: cdouble): JSValue
 
 proc JS_NewAtomLen*(ctx: JSContext, str: cstring, len: csize_t): JSAtom
+proc JS_NewAtomUInt32*(ctx: JSContext, u: uint32): JSAtom
 proc JS_ValueToAtom*(ctx: JSContext, val: JSValue): JSAtom
 proc JS_AtomToValue*(ctx: JSContext, atom: JSAtom): JSValue
 proc JS_AtomToCString*(ctx: JSContext, atom: JSAtom): cstring
@@ -490,6 +491,9 @@ proc JS_SetRuntimeOpaque*(rt: JSRuntime, p: pointer)
 proc JS_SetContextOpaque*(ctx: JSContext, opaque: pointer)
 proc JS_GetContextOpaque*(ctx: JSContext): pointer
 
+proc js_malloc*(ctx: JSContext, size: csize_t): pointer
+proc js_mallocz*(ctx: JSContext, size: csize_t): pointer
+proc js_realloc*(ctx: JSContext, p: pointer, size: csize_t): pointer
 proc js_free_rt*(rt: JSRuntime, p: pointer)
 proc js_free*(ctx: JSContext, p: pointer)
 
diff --git a/src/config/config.nim b/src/config/config.nim
index 5f749708..63438c55 100644
--- a/src/config/config.nim
+++ b/src/config/config.nim
@@ -9,6 +9,7 @@ import config/toml
 import io/urlfilter
 import js/error
 import js/javascript
+import js/propertyenumlist
 import js/regex
 import loader/headers
 import loader/loader
@@ -183,12 +184,20 @@ proc setter(a: ptr ActionMap, k, v: string) {.jssetprop.} =
       a[][teststr] = "client.feedNext()"
     teststr.setLen(i)
 
-proc delete(a: ptr Actionmap, k: string): bool {.jsdelprop.} =
+proc delete(a: ptr ActionMap, k: string): bool {.jsdelprop.} =
   let k = getRealKey(k)
   let ina = k in a[]
   a[].t.del(k)
   return ina
 
+func names(ctx: JSContext, a: ptr ActionMap): JSPropertyEnumList
+    {.jspropnames.} =
+  let L = uint32(a[].t.len)
+  var list = newJSPropertyEnumList(ctx, L)
+  for key in a[].t.keys:
+    list.add(key)
+  return list
+
 proc bindPagerKey(config: Config, key, action: string) {.jsfunc.} =
   (addr config.page).setter(key, action)
 
diff --git a/src/css/cascade.nim b/src/css/cascade.nim
index 1eaa643c..c0900394 100644
--- a/src/css/cascade.nim
+++ b/src/css/cascade.nim
@@ -1,6 +1,5 @@
 import algorithm
 import options
-import streams
 import strutils
 
 import css/cssparser
@@ -254,10 +253,9 @@ proc applyDeclarations(styledNode: StyledNode, parent: CSSComputedValues,
     builder.addValues(rule[pseudo], ORIGIN_AUTHOR)
   if styledNode.node != nil:
     let element = Element(styledNode.node)
-    let style = element.attr("style")
-    if style.len > 0:
-      let inline_rules = newStringStream(style).parseListOfDeclarations2()
-      builder.addValues(inline_rules, ORIGIN_AUTHOR)
+    let style = element.style_cached
+    if style != nil:
+      builder.addValues(style.decls, ORIGIN_AUTHOR)
     builder.preshints = element.calcPresentationalHints()
 
   styledNode.computed = builder.buildComputedValues()
diff --git a/src/html/dom.nim b/src/html/dom.nim
index ee669036..d143d5ea 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -23,6 +23,7 @@ import js/error
 import js/fromjs
 import js/javascript
 import js/opaque
+import js/propertyenumlist
 import js/timeout
 import js/tojs
 import loader/loader
@@ -228,6 +229,11 @@ type
     attributes* {.jsget.}: NamedNodeMap
     hover*: bool
     invalid*: bool
+    style_cached*: CSSStyleDeclaration
+
+  CSSStyleDeclaration* = ref object
+    decls*: seq[CSSDeclaration]
+    element: Element
 
   HTMLElement* = ref object of Element
 
@@ -412,6 +418,7 @@ jsDestructor(Attr)
 jsDestructor(NamedNodeMap)
 jsDestructor(CanvasRenderingContext2D)
 jsDestructor(TextMetrics)
+jsDestructor(CSSStyleDeclaration)
 
 proc parseColor(element: Element, s: string): RGBAColor
 
@@ -797,6 +804,7 @@ const ReflectTable0 = [
 ]
 
 # Forward declarations
+func attr*(element: Element, s: string): string {.inline.}
 func attrb*(element: Element, s: string): bool
 proc attr*(element: Element, name, value: string)
 func baseURL*(document: Document): URL
@@ -1146,19 +1154,54 @@ func item(nodeList: NodeList, i: int): Node {.jsfunc.} =
 func getter(nodeList: NodeList, i: int): Option[Node] {.jsgetprop.} =
   return option(nodeList.item(i))
 
+func names(ctx: JSContext, nodeList: NodeList): JSPropertyEnumList
+    {.jspropnames.} =
+  let L = nodeList.length
+  var list = newJSPropertyEnumList(ctx, L)
+  for u in 0 ..< L:
+    list.add(u)
+  return list
+
 # HTMLCollection
 proc length(collection: HTMLCollection): uint32 {.jsfget.} =
   return uint32(collection.len)
 
-func hasprop(collection: HTMLCollection, i: int): bool {.jshasprop.} =
-  return i < collection.len
+func hasprop(collection: HTMLCollection, u: uint32): bool {.jshasprop.} =
+  return u < collection.length
 
-func item(collection: HTMLCollection, i: int): Element {.jsfunc.} =
-  if i < collection.len:
-    return Element(collection.snapshot[i])
+func item(collection: HTMLCollection, u: uint32): Element {.jsfunc.} =
+  if u < collection.length:
+    return Element(collection.snapshot[int(u)])
 
-func getter(collection: HTMLCollection, i: int): Option[Element] {.jsgetprop.} =
-  return option(collection.item(i))
+func namedItem(collection: HTMLCollection, s: string): Element {.jsfunc.} =
+  for it in collection.snapshot:
+    let it = Element(it)
+    if it.id == s or it.namespace == Namespace.HTML and it.attr("name") == s:
+      return it
+
+func getter[T: uint32|string](collection: HTMLCollection, u: T):
+    Option[Element] {.jsgetprop.} =
+  when T is uint32:
+    return option(collection.item(u))
+  else:
+    return option(collection.namedItem(u))
+
+func names(ctx: JSContext, collection: HTMLCollection): JSPropertyEnumList
+    {.jspropnames.} =
+  let L = collection.length
+  var list = newJSPropertyEnumList(ctx, L)
+  var ids: OrderedSet[string]
+  for u in 0 ..< L:
+    list.add(u)
+    let elem = collection.item(u)
+    if elem.id != "":
+      ids.incl(elem.id)
+    if elem.namespace == Namespace.HTML:
+      let name = elem.attr("name")
+      ids.incl(name)
+  for id in ids:
+    list.add(id)
+  return list
 
 # HTMLAllCollection
 proc length(collection: HTMLAllCollection): uint32 {.jsfget.} =
@@ -1174,6 +1217,14 @@ func item(collection: HTMLAllCollection, i: int): Element {.jsfunc.} =
 func getter(collection: HTMLAllCollection, i: int): Option[Element] {.jsgetprop.} =
   return option(collection.item(i))
 
+func names(ctx: JSContext, collection: HTMLAllCollection): JSPropertyEnumList
+    {.jspropnames.} =
+  let L = collection.length
+  var list = newJSPropertyEnumList(ctx, L)
+  for u in 0 ..< L:
+    list.add(u)
+  return list
+
 proc all(document: Document): HTMLAllCollection {.jsfget.} =
   if document.all_cached == nil:
     document.all_cached = newCollection[HTMLAllCollection](
@@ -1427,6 +1478,20 @@ func getter[T: int|string](map: NamedNodeMap, i: T): Option[Attr] {.jsgetprop.}
   else:
     return map.getNamedItem(i)
 
+func names(ctx: JSContext, map: NamedNodeMap): JSPropertyEnumList
+    {.jspropnames.} =
+  let len = if map.element.namespace == Namespace.HTML:
+    uint32(map.attrlist.len + map.element.attrs.len)
+  else:
+    uint32(map.attrlist.len)
+  var list = newJSPropertyEnumList(ctx, len)
+  for u in 0 ..< len:
+    list.add(u)
+  if map.element.namespace == Namespace.HTML:
+    for name in map.element.attrs.keys:
+      list.add(name)
+  return list
+
 func length(characterData: CharacterData): uint32 {.jsfget.} =
   return uint32(characterData.data.utf16Len)
 
@@ -2188,6 +2253,16 @@ proc delAttr(element: Element, name: string) =
   if i != -1:
     element.delAttr(i)
 
+proc newCSSStyleDeclaration(element: Element, value: string):
+    CSSStyleDeclaration =
+  let inline_rules = newStringStream(value).parseListOfDeclarations2()
+  return CSSStyleDeclaration(decls: inline_rules, element: element)
+
+proc style(element: Element): CSSStyleDeclaration {.jsfget.} =
+  if element.style_cached == nil:
+    element.style_cached = CSSStyleDeclaration(element: element)
+  return element.style_cached
+
 proc reflectAttrs(element: Element, name, value: string) =
   template reflect_str(element: Element, n: static string, val: untyped) =
     if name == n:
@@ -2205,24 +2280,26 @@ proc reflectAttrs(element: Element, name, value: string) =
     for x in value.split(AsciiWhitespace):
       if x != "" and x notin element.classList:
         element.classList.toks.add(x)
-    return
-  case element.tagType
-  of TAG_INPUT:
-    let input = HTMLInputElement(element)
-    input.reflect_str "value", value
-    input.reflect_str "type", inputType, inputType
-    input.reflect_bool "checked", checked
-  of TAG_OPTION:
-    let option = HTMLOptionElement(element)
-    option.reflect_bool "selected", selected
-  of TAG_BUTTON:
-    let button = HTMLButtonElement(element)
-    button.reflect_str "type", ctype, (func(s: string): ButtonType =
-      case s
-      of "submit": return BUTTON_SUBMIT
-      of "reset": return BUTTON_RESET
-      of "button": return BUTTON_BUTTON)
-  else: discard
+  elif name == "style":
+    element.style_cached = newCSSStyleDeclaration(element, value)
+  else:
+    case element.tagType
+    of TAG_INPUT:
+      let input = HTMLInputElement(element)
+      input.reflect_str "value", value
+      input.reflect_str "type", inputType, inputType
+      input.reflect_bool "checked", checked
+    of TAG_OPTION:
+      let option = HTMLOptionElement(element)
+      option.reflect_bool "selected", selected
+    of TAG_BUTTON:
+      let button = HTMLButtonElement(element)
+      button.reflect_str "type", ctype, (func(s: string): ButtonType =
+        case s.toLowerAscii()
+        of "submit": return BUTTON_SUBMIT
+        of "reset": return BUTTON_RESET
+        of "button": return BUTTON_BUTTON)
+    else: discard
 
 proc attr0(element: Element, name, value: string) =
   element.attrs.withValue(name, val):
@@ -3300,4 +3377,5 @@ proc addDOMModule*(ctx: JSContext) =
   ctx.registerType(NamedNodeMap)
   ctx.registerType(CanvasRenderingContext2D)
   ctx.registerType(TextMetrics)
+  ctx.registerType(CSSStyleDeclaration)
   ctx.registerElements(nodeCID)
diff --git a/src/js/javascript.nim b/src/js/javascript.nim
index 996137cd..e682098a 100644
--- a/src/js/javascript.nim
+++ b/src/js/javascript.nim
@@ -32,6 +32,9 @@
 # {.jsdelprop.} for property deletion. It is like the deleteProperty method
 #   of Proxy. Must return true if deleted, false if not deleted.
 # {.jshasprop.} for overriding has_property. Must return a boolean.
+# {.jspropnames.} overrides get_own_property_names. Must return a
+#   JSPropertyEnumList object.
+# UncheckedArray[JSPropertyEnum]
 
 import macros
 import options
@@ -98,6 +101,7 @@ type
     PROPERTY_SET = "js_prop_set"
     PROPERTY_DEL = "js_prop_del"
     PROPERTY_HAS = "js_prop_has"
+    PROPERTY_NAMES = "js_prop_names"
     FINALIZER = "js_fin"
 
 var runtimes {.threadVar.}: seq[JSRuntime]
@@ -471,6 +475,15 @@ template getJSSetterParams(): untyped =
     newIdentDefs(ident("val"), quote do: JSValue),
   ]
 
+template getJSPropNamesParams(): untyped =
+  [
+    (quote do: cint),
+    newIdentDefs(ident("ctx"), quote do: JSContext),
+    newIdentDefs(ident("ptab"), quote do: ptr JSPropertyEnumArray),
+    newIdentDefs(ident("plen"), quote do: ptr uint32),
+    newIdentDefs(ident("obj"), quote do: JSValue)
+  ]
+
 template fromJS_or_return*(t, ctx, val: untyped): untyped =
   (
     let x = fromJS[t](ctx, val)
@@ -563,6 +576,10 @@ proc addUnionParam0(gen: var JSFuncGenerator, tt: NimNode, s: NimNode, val: NimN
     elif g == bool.getTypeInst():
       hasBoolean = true
     elif g == int.getTypeInst(): #TODO should be SomeNumber
+      assert numg.isNone
+      numg = some(g)
+    elif g == uint32.getTypeInst(): #TODO should be SomeNumber
+      assert numg.isNone
       numg = some(g)
     elif g.getTypeInst().getTypeImpl().kind == nnkRefTy:
       # Assume it's ref object.
@@ -789,7 +806,8 @@ func getFuncName(fun: NimNode, jsname: string): string =
   return x
 
 func getErrVal(t: BoundFunctionType): NimNode =
-  if t in {PROPERTY_GET, PROPERTY_SET, PROPERTY_DEL, PROPERTY_HAS}:
+  if t in {PROPERTY_GET, PROPERTY_SET, PROPERTY_DEL, PROPERTY_HAS,
+      PROPERTY_NAMES}:
     return quote do: cint(-1)
   return quote do: JS_EXCEPTION
 
@@ -976,6 +994,26 @@ macro jsdelprop*(fun: typed) =
   gen.registerFunction()
   return newStmtList(fun, jsProc)
 
+macro jspropnames*(fun: typed) =
+  var gen = setupGenerator(fun, PROPERTY_NAMES, thisname = some("obj"))
+  if gen.newName.strVal in existing_funcs:
+    #TODO TODO TODO ditto
+    error("Function overloading hasn't been implemented yet...")
+  gen.addFixParam("obj")
+  gen.finishFunCallList()
+  let jfcl = gen.jsFunCallList
+  let dl = gen.dielabel
+  gen.jsCallAndRet = quote do:
+    block `dl`:
+      let retv = `jfcl`
+      ptab[] = retv.buffer
+      plen[] = retv.len
+      return cint(0)
+    return cint(-1)
+  let jsProc = gen.newJSProc(getJSPropNamesParams(), false)
+  gen.registerFunction()
+  return newStmtList(fun, jsProc)
+
 macro jsfgetn(jsname: static string, uf: static bool, fun: typed) =
   var gen = setupGenerator(fun, GETTER, jsname = jsname, unforgeable = uf)
   if gen.actualMinArgs != 0 or gen.funcParams.len != gen.minArgs:
@@ -1221,6 +1259,7 @@ type RegistryInfo = object
   propSetFun: NimNode # custom set function ident
   propDelFun: NimNode # custom del function ident
   propHasFun: NimNode # custom has function ident
+  propNamesFun: NimNode # custom property names function ident
   finFun: NimNode # finalizer ident
   finName: NimNode # finalizer wrapper ident
   dfin: NimNode # CheckDestroy finalizer ident
@@ -1252,7 +1291,8 @@ proc newRegistryInfo(t: NimNode, name: string): RegistryInfo =
     propGetFun: newNilLit(),
     propSetFun: newNilLit(),
     propDelFun: newNilLit(),
-    propHasFun: newNilLit()
+    propHasFun: newNilLit(),
+    propNamesFun: newNilLit()
   )
   if info.tname notin js_dtors:
     warning("No destructor has been defined for type " & info.tname)
@@ -1368,6 +1408,10 @@ proc bindFunctions(stmts: NimNode, info: var RegistryInfo) =
         if info.propHasFun.kind != nnkNilLit:
           error("Class " & info.tname & " has 2+ hasprop getters.")
         info.propHasFun = f1
+      of PROPERTY_NAMES:
+        if info.propNamesFun.kind != nnkNilLit:
+          error("Class " & info.tname & " has 2+ propnames getters.")
+        info.propNamesFun = f1
       of FINALIZER:
         f0 = fun.name
         info.finFun = ident(f0)
@@ -1463,16 +1507,19 @@ proc bindEndStmts(endstmts: NimNode, info: RegistryInfo) =
   if info.propGetFun.kind != nnkNilLit or
       info.propSetFun.kind != nnkNilLit or
       info.propDelFun.kind != nnkNilLit or
-      info.propHasFun.kind != nnkNilLit:
+      info.propHasFun.kind != nnkNilLit or
+      info.propNamesFun.kind != nnkNilLit:
     let propGetFun = info.propGetFun
     let propSetFun = info.propSetFun
     let propDelFun = info.propDelFun
     let propHasFun = info.propHasFun
+    let propNamesFun = info.propNamesFun
     endstmts.add(quote do:
       # No clue how to do this in pure nim.
       {.emit: ["""
 static JSClassExoticMethods exotic = {
 	.get_own_property = """, `propGetFun`, """,
+        .get_own_property_names = """, `propNamesFun`, """,
 	.has_property = """, `propHasFun`, """,
 	.set_property = """, `propSetFun`, """,
 	.delete_property = """, `propDelFun`, """
diff --git a/src/js/propertyenumlist.nim b/src/js/propertyenumlist.nim
new file mode 100644
index 00000000..98ad2a4f
--- /dev/null
+++ b/src/js/propertyenumlist.nim
@@ -0,0 +1,42 @@
+import bindings/quickjs
+
+type
+  JSPropertyEnumArray* = ptr UncheckedArray[JSPropertyEnum]
+
+  JSPropertyEnumList* = object
+    buffer*: JSPropertyEnumArray
+    size: uint32
+    len*: uint32
+    ctx: JSContext
+
+  JSPropertyEnumWrapper* = object
+    is_enumerable: bool
+    name: string
+
+func newJSPropertyEnumList*(ctx: JSContext, size: uint32): JSPropertyEnumList =
+  let p = js_malloc(ctx, csize_t(sizeof(JSPropertyEnum)) * csize_t(size))
+  let buffer = cast[JSPropertyEnumArray](p)
+  return JSPropertyEnumList(
+    ctx: ctx,
+    buffer: buffer,
+    size: size
+  )
+
+proc grow(this: var JSPropertyEnumList) =
+  this.size *= 2
+  let p = js_realloc(this.ctx, this.buffer, csize_t(this.size))
+  this.buffer = cast[JSPropertyEnumArray](p)
+
+proc add*(this: var JSPropertyEnumList, val: uint32) =
+  let i = this.len
+  inc this.len
+  if this.size < this.len:
+    this.grow()
+  this.buffer[i].atom = JS_NewAtomUInt32(this.ctx, val)
+
+proc add*(this: var JSPropertyEnumList, val: string) =
+  let i = this.len
+  inc this.len
+  if this.size < this.len:
+    this.grow()
+  this.buffer[i].atom = JS_NewAtomLen(this.ctx, cstring(val), csize_t(val.len))