diff options
author | bptato <nincsnevem662@gmail.com> | 2023-08-31 22:52:28 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2023-08-31 22:52:28 +0200 |
commit | 3a7d189d88747d119ab475c96e067b86c4cd2957 (patch) | |
tree | 044978abafa0f2eb288bdaf6c0e840b530fadd2f | |
parent | 40aef0b310bb84628b759bdb3f17ff7229afc634 (diff) | |
download | chawan-3a7d189d88747d119ab475c96e067b86c4cd2957.tar.gz |
buffer: basic click event support
Mostly a proof of concept. Just bubble it unconditionally for now and never prevent default. Also, fix fromJSFunction by Dup'ing its closure JSValue.
-rw-r--r-- | src/buffer/buffer.nim | 52 | ||||
-rw-r--r-- | src/html/event.nim | 22 | ||||
-rw-r--r-- | src/js/fromjs.nim | 17 | ||||
-rw-r--r-- | src/js/javascript.nim | 2 | ||||
-rw-r--r-- | src/js/tojs.nim | 4 |
5 files changed, 81 insertions, 16 deletions
diff --git a/src/buffer/buffer.nim b/src/buffer/buffer.nim index c53641e9..0b3c5d95 100644 --- a/src/buffer/buffer.nim +++ b/src/buffer/buffer.nim @@ -20,6 +20,7 @@ import css/values import html/chadombuilder import html/dom import html/env +import html/event import img/png import io/connecterror import io/loader @@ -111,6 +112,7 @@ type tasks: array[BufferCommand, int] #TODO this should have arguments savetask: bool hovertext: array[HoverType, string] + estream: Stream # error stream InterfaceOpaque = ref object stream: Stream @@ -329,10 +331,23 @@ func getClickHover(styledNode: StyledNode): string = return "<textarea>" else: discard -func getCursorClickable(buffer: Buffer, cursorx, cursory: int): Element = +func getCursorStyledNode(buffer: Buffer, cursorx, cursory: int): StyledNode = let i = buffer.lines[cursory].findFormatN(cursorx) - 1 if i >= 0: - return buffer.lines[cursory].formats[i].node.getClickable() + return buffer.lines[cursory].formats[i].node + +func getCursorElement(buffer: Buffer, cursorx, cursory: int): Element = + let styledNode = buffer.getCursorStyledNode(cursorx, cursory) + if styledNode == nil or styledNode.node == nil: + return nil + if styledNode.t == STYLED_ELEMENT: + return Element(styledNode.node) + return styledNode.node.parentElement + +func getCursorClickable(buffer: Buffer, cursorx, cursory: int): Element = + let styledNode = buffer.getCursorStyledNode(cursorx, cursory) + if styledNode != nil: + return styledNode.getClickable() func cursorBytes(buffer: Buffer, y: int, cc: int): int = let line = buffer.lines[y].str @@ -1176,11 +1191,39 @@ proc click(buffer: Buffer, clickable: Element): ClickResult = else: result.repaint = buffer.restoreFocus() +proc dispatchEvent(buffer: Buffer, ctype: string, elem: Element): bool = + var called = false + for a in elem.branch: + var stop = false + for el in a.eventListeners: + if el.ctype == "click": + let event = newEvent(buffer.window.jsctx, ctype, elem, a) + let e = el.callback(event) + called = true + if e.isErr: + buffer.window.jsctx.writeException(buffer.estream) + if FLAG_STOP_IMMEDIATE_PROPAGATION in event.flags: + stop = true + break + if FLAG_STOP_PROPAGATION in event.flags: + stop = true + if stop: + break + return called + proc click*(buffer: Buffer, cursorx, cursory: int): ClickResult {.proxy.} = if buffer.lines.len <= cursory: return + var called = false + if buffer.config.scripting: + let elem = buffer.getCursorElement(cursorx, cursory) + if buffer.dispatchEvent("click", elem): + buffer.do_reshape() let clickable = buffer.getCursorClickable(cursorx, cursory) if clickable != nil: - return buffer.click(clickable) + var res = buffer.click(clickable) + res.repaint = called + return res + return ClickResult(repaint: called) proc select*(buffer: Buffer, selected: seq[int]): ClickResult {.proxy.} = if buffer.document.focus != nil and @@ -1347,7 +1390,7 @@ proc runBuffer(buffer: Buffer, rfd: int) = buffer.handleError(event.fd, event.errorCode) if not buffer.alive: break - if Event.Timer in event.events: + if selectors.Event.Timer in event.events: assert buffer.window != nil assert buffer.window.timeouts.runTimeoutFd(event.fd) buffer.window.runJSJobs() @@ -1379,6 +1422,7 @@ proc launchBuffer*(config: BufferConfig, source: BufferSource, buffer.window = newWindow(buffer.config.scripting, buffer.selector, buffer.attrs, proc(url: URL) = buffer.navigate(url), some(buffer.loader)) let socks = connectSocketStream(mainproc, false) + buffer.estream = newFileStream(stderr) socks.swrite(getpid()) buffer.pstream = socks let rfd = int(socks.source.getFd()) diff --git a/src/html/event.nim b/src/html/event.nim index 5f841fd1..a6ab2e36 100644 --- a/src/html/event.nim +++ b/src/html/event.nim @@ -15,7 +15,7 @@ type AT_TARGET = 2u16 BUBBLING_PHASE = 3u16 - EventFlag = enum + EventFlag* = enum FLAG_STOP_PROPAGATION FLAG_STOP_IMMEDIATE_PROPAGATION FLAG_CANCELED @@ -26,14 +26,14 @@ type Event* = ref object of RootObj ctype {.jsget: "type".}: string - target {.jsget.}: EventTarget + target* {.jsget.}: EventTarget currentTarget {.jsget.}: EventTarget eventPhase {.jsget.}: uint16 bubbles {.jsget.}: bool cancelable {.jsget.}: bool #TODO DOMHighResTimeStamp? timeStamp {.jsget.}: float64 - flags: set[EventFlag] + flags*: set[EventFlag] isTrusted {.jsufget.}: bool CustomEvent* = ref object of Event @@ -47,9 +47,9 @@ type EventListenerCallback = proc (event: Event): Err[JSError] - EventListener = ref object - ctype: string - callback: EventListenerCallback + EventListener* = ref object + ctype*: string + callback*: EventListenerCallback capture: bool passive: Opt[bool] once: bool @@ -93,6 +93,13 @@ proc newEvent(ctx: JSContext, ctype: string, eventInitDict = JS_UNDEFINED): event.ctype = ctype return ok(event) +proc newEvent*(ctx: JSContext, ctype: string, target, currentTarget: EventTarget): Event = + return Event( + ctype: ctype, + target: target, + currentTarget: currentTarget + ) + proc initialize(this: Event, ctype: string, bubbles, cancelable: bool) = this.flags.incl(FLAG_INITIALIZED) this.isTrusted = false @@ -231,10 +238,11 @@ proc flattenMore(ctx: JSContext, options: JSValue): passive = opt(x.get) return (capture, once, passive) -proc addEventListener(ctx: JSContext, eventTarget: EventTarget, +proc addEventListener(ctx: JSContext, eventTarget: EventTarget, ctype: string, callback: EventListenerCallback, options = JS_UNDEFINED) {.jsfunc.} = let (capture, once, passive) = flattenMore(ctx, options) let listener = EventListener( + ctype: ctype, capture: capture, passive: passive, once: once, diff --git a/src/js/fromjs.nim b/src/js/fromjs.nim index c7e27110..310550e4 100644 --- a/src/js/fromjs.nim +++ b/src/js/fromjs.nim @@ -242,6 +242,8 @@ proc fromJSSet[T](ctx: JSContext, val: JSValue): Opt[set[T]] = return ok(s) proc fromJSTable[A, B](ctx: JSContext, val: JSValue): JSResult[Table[A, B]] = + if not JS_IsObject(val): + return err(newTypeError("object expected")) var ptab: ptr UncheckedArray[JSPropertyEnum] var plen: uint32 let flags = cint(JS_GPN_STRING_MASK) @@ -267,14 +269,13 @@ proc fromJSTable[A, B](ctx: JSContext, val: JSValue): JSResult[Table[A, B]] = #TODO varargs proc fromJSFunction1*[T, U](ctx: JSContext, val: JSValue): proc(x: U): JSResult[T] = + #TODO this leaks memory! let dupval = JS_DupValue(ctx, JS_DupValue(ctx, val)) # save return proc(x: U): JSResult[T] = var arg1 = toJS(ctx, x) #TODO exceptions? let ret = JS_Call(ctx, dupval, JS_UNDEFINED, 1, addr arg1) - when T isnot void: - result = fromJS[T](ctx, ret) - JS_FreeValue(ctx, dupval) + result = fromJS[T](ctx, ret) JS_FreeValue(ctx, ret) proc isErrType(rt: NimNode): bool = @@ -312,6 +313,8 @@ macro unpackArg0(f: typed) = proc fromJSFunction[T](ctx: JSContext, val: JSValue): JSResult[T] = #TODO all args... + if not JS_IsFunction(ctx, val): + return err(newTypeError("function expected")) return ok( fromJSFunction1[ typeof(unpackReturnType(T)), @@ -394,6 +397,12 @@ proc fromJSPObj0(ctx: JSContext, val: JSValue, t: string): proc fromJSObject[T: ref object](ctx: JSContext, val: JSValue): JSResult[T] = return ok(cast[T](?fromJSPObj0(ctx, val, $T))) +proc fromJSVoid(ctx: JSContext, val: JSValue): JSResult[void] = + if JS_IsException(val): + #TODO maybe wrap or something + return err() + return ok() + type FromJSAllowedT = (object and not (Result|Option|Table|JSValue)) proc fromJS*[T](ctx: JSContext, val: JSValue): JSResult[T] = @@ -430,6 +439,8 @@ proc fromJS*[T](ctx: JSContext, val: JSValue): JSResult[T] = return ok(val) elif T is ref object: return fromJSObject[T](ctx, val) + elif T is void: + return fromJSVoid(ctx, val) elif compiles(fromJS2(ctx, val, result)): fromJS2(ctx, val, result) else: diff --git a/src/js/javascript.nim b/src/js/javascript.nim index 06d1a577..b1d16734 100644 --- a/src/js/javascript.nim +++ b/src/js/javascript.nim @@ -170,7 +170,7 @@ proc setInterruptHandler*(rt: JSRuntime, cb: JSInterruptHandler, opaque: pointer proc writeException*(ctx: JSContext, s: Stream) = let ex = JS_GetException(ctx) let str = fromJS[string](ctx, ex) - if str.issome: + if str.isSome: s.write(str.get & '\n') let stack = JS_GetPropertyStr(ctx, ex, cstring("stack")); if not JS_IsUndefined(stack): diff --git a/src/js/tojs.nim b/src/js/tojs.nim index 4cde2e15..050af5e6 100644 --- a/src/js/tojs.nim +++ b/src/js/tojs.nim @@ -243,7 +243,9 @@ proc toJS*(ctx: JSContext, err: JSError): JSValue = if JS_IsException(msg): return msg let ctor = ctx.getOpaque().err_ctors[err.e] - return JS_CallConstructor(ctx, ctor, 1, addr msg) + let ret = JS_CallConstructor(ctx, ctor, 1, addr msg) + JS_FreeValue(ctx, msg) + return ret proc toJS*(ctx: JSContext, f: JSCFunction): JSValue = return JS_NewCFunction(ctx, f, cstring"", 0) |