diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bindings/quickjs.nim | 6 | ||||
-rw-r--r-- | src/config/config.nim | 11 | ||||
-rw-r--r-- | src/css/cascade.nim | 8 | ||||
-rw-r--r-- | src/html/dom.nim | 128 | ||||
-rw-r--r-- | src/js/javascript.nim | 53 | ||||
-rw-r--r-- | src/js/propertyenumlist.nim | 42 |
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)) |