diff options
author | bptato <nincsnevem662@gmail.com> | 2025-05-05 19:30:20 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2025-05-05 19:33:02 +0200 |
commit | ee6033b9024ca836019e7212ff18b24c915e2d6f (patch) | |
tree | 10c23c6acdd16ca355f53b0a88676985691e1cb9 | |
parent | 32ab5acba853a9940e49250c7a27bcb18fe8af75 (diff) | |
download | chawan-ee6033b9024ca836019e7212ff18b24c915e2d6f.tar.gz |
event: implement capturing phase, misc improvements
Fixes acid3 tests 31, 32
-rw-r--r-- | src/html/dom.nim | 19 | ||||
-rw-r--r-- | src/html/event.nim | 44 | ||||
-rw-r--r-- | src/html/xmlhttprequest.nim | 15 | ||||
-rw-r--r-- | src/server/buffer.nim | 48 | ||||
-rw-r--r-- | test/net/xhr.html | 5 |
5 files changed, 90 insertions, 41 deletions
diff --git a/src/html/dom.nim b/src/html/dom.nim index bb2c4982..90a2e164 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -2819,6 +2819,12 @@ proc blur(ctx: JSContext; element: Element) {.jsfunc.} = if element.document.focus == element: element.document.setFocus(nil) +proc click(ctx: JSContext; element: Element) {.jsfunc.} = + #TODO should also trigger click on inputs. + let event = newEvent(satClick.toAtom(), element, bubbles = true, + cancelable = true) + discard ctx.dispatch(element, event) + proc scrollTo(element: Element) {.jsfunc.} = discard #TODO maybe in app mode? @@ -2851,9 +2857,10 @@ func findAutoFocus*(document: Document): Element = proc fireEvent*(window: Window; event: Event; target: EventTarget) = discard window.jsctx.dispatch(target, event) -proc fireEvent*(window: Window; name: StaticAtom; target: EventTarget) = - let event = newEvent(name.toAtom(), target) - event.isTrusted = true +proc fireEvent*(window: Window; name: StaticAtom; target: EventTarget; + bubbles, cancelable, trusted: bool) = + let event = newEvent(name.toAtom(), target, bubbles, cancelable) + event.isTrusted = trusted window.fireEvent(event, target) proc parseColor(element: Element; s: string): ARGBColor = @@ -4357,7 +4364,8 @@ proc loadResource*(window: Window; image: HTMLImageElement) = image.invalidate() #TODO fire error on error if window.settings.scripting != smFalse: - window.fireEvent(satLoad, image) + window.fireEvent(satLoad, image, bubbles = false, + cancelable = false, trusted = true) ) ) window.pendingResources.add(p) @@ -5803,7 +5811,8 @@ proc createEvent(ctx: JSContext; document: Document; atom: CAtom): of satCustomevent: return ok(ctx.newCustomEvent(satUempty.toAtom())) of satEvent, satEvents, satSvgevents: - return ok(newEvent(satUempty.toAtom(), nil)) + return ok(newEvent(satUempty.toAtom(), nil, + bubbles = false, cancelable = false)) of satUievent, satUievents: return ok(newUIEvent(satUempty.toAtom())) else: diff --git a/src/html/event.nim b/src/html/event.nim index 132ea926..7a4d030d 100644 --- a/src/html/event.nim +++ b/src/html/event.nim @@ -37,10 +37,10 @@ type eventPhase {.jsget.}: uint16 bubbles {.jsget.}: bool cancelable {.jsget.}: bool - #TODO DOMHighResTimeStamp? - timeStamp {.jsget.}: float64 flags*: set[EventFlag] isTrusted* {.jsufget.}: bool + #TODO DOMHighResTimeStamp? + timeStamp {.jsget.}: float64 CustomEvent* = ref object of Event detail {.jsget.}: JSValue @@ -131,11 +131,14 @@ proc newEvent(ctx: JSContext; ctype: CAtom; eventInitDict = EventInit()): event.innerEventCreationSteps(eventInitDict) return event -proc newEvent*(ctype: CAtom; target: EventTarget): Event = +proc newEvent*(ctype: CAtom; target: EventTarget; bubbles, cancelable: bool): + Event = return Event( ctype: ctype, target: target, - currentTarget: target + currentTarget: target, + bubbles: bubbles, + cancelable: cancelable ) proc initialize(this: Event; ctype: CAtom; bubbles, cancelable: bool) = @@ -352,8 +355,7 @@ proc invoke(ctx: JSContext; listener: EventListener; event: Event): JSValue = # Apparently it's a bad idea to call a function that can then delete # the reference it was called from. let callback = JS_DupValue(ctx, listener.callback) - let ret = JS_Call(ctx, callback, jsTarget, 1, - jsEvent.toJSValueArray()) + let ret = JS_Call(ctx, callback, jsTarget, 1, jsEvent.toJSValueArray()) JS_FreeValue(ctx, callback) JS_FreeValue(ctx, jsTarget) JS_FreeValue(ctx, jsEvent) @@ -517,13 +519,13 @@ proc hasEventListener*(eventTarget: EventTarget; ctype: CAtom): bool = return false proc dispatchEvent0(ctx: JSContext; event: Event; currentTarget: EventTarget; - stop, canceled: var bool) = + stop, canceled: var bool; capture: bool) = event.currentTarget = currentTarget var els = currentTarget.eventListeners # copy intentionally for el in els: if JS_IsUndefined(el.callback): continue # removed, presumably by a previous handler - if el.ctype == event.ctype: + if el.ctype == event.ctype and el.capture == capture: let e = ctx.invoke(el, event) if JS_IsException(e): ctx.logException() @@ -536,15 +538,31 @@ proc dispatchEvent0(ctx: JSContext; event: Event; currentTarget: EventTarget; break proc dispatch*(ctx: JSContext; target: EventTarget; event: Event): bool = - #TODO this is far from being compliant var canceled = false var stop = false event.flags.incl(efDispatch) event.target = target - var target = target - while target != nil and not stop: - ctx.dispatchEvent0(event, target, stop, canceled) - target = ctx.getParentImpl(target, event) + var it = target + var targets: seq[EventTarget] = @[] + while it != nil: + targets.add(it) + it = ctx.getParentImpl(it, event) + event.eventPhase = 1 + for i in countdown(targets.high, 1): + if stop: + break + let it = targets[i] + ctx.dispatchEvent0(event, it, stop, canceled, capture = true) + event.eventPhase = 2 + ctx.dispatchEvent0(event, target, stop, canceled, capture = true) + ctx.dispatchEvent0(event, target, stop, canceled, capture = false) + if event.bubbles: + event.eventPhase = 3 + for target in targets: + if stop: + break + ctx.dispatchEvent0(event, target, stop, canceled, capture = false) + event.eventPhase = 0 event.flags.excl(efDispatch) return canceled diff --git a/src/html/xmlhttprequest.nim b/src/html/xmlhttprequest.nim index 47a6bef9..1879bb29 100644 --- a/src/html/xmlhttprequest.nim +++ b/src/html/xmlhttprequest.nim @@ -118,6 +118,10 @@ proc parseMethod(s: string): DOMResult[HttpMethod] = else: errDOMException("Invalid method", "SyntaxError") +proc fireReadyStateChangeEvent(window: Window; target: EventTarget) = + window.fireEvent(satReadystatechange, target, bubbles = false, + cancelable = false, trusted = true) + proc open(ctx: JSContext; this: XMLHttpRequest; httpMethod, url: string; misc: varargs[JSValueConst]): Err[DOMException] {.jsfunc.} = let httpMethod = ?parseMethod(httpMethod) @@ -156,7 +160,7 @@ proc open(ctx: JSContext; this: XMLHttpRequest; httpMethod, url: string; #TODO response object, received bytes if this.readyState != xhrsOpened: this.readyState = xhrsOpened - global.fireEvent(satReadystatechange, this) + global.fireReadyStateChangeEvent(this) return ok() proc checkOpened(this: XMLHttpRequest): DOMResult[void] = @@ -207,6 +211,7 @@ proc fireProgressEvent(window: Window; target: EventTarget; name: StaticAtom; total: length, lengthComputable: length != 0 )) + event.isTrusted = true window.fireEvent(event, target) proc errorSteps(window: Window; this: XMLHttpRequest; name: StaticAtom) = @@ -214,7 +219,7 @@ proc errorSteps(window: Window; this: XMLHttpRequest; name: StaticAtom) = this.response = makeNetworkError() this.flags.excl(xhrfSend) if xhrfSync notin this.flags: - window.fireEvent(satReadystatechange, this) + window.fireReadyStateChangeEvent(this) if xhrfUploadComplete notin this.flags: this.flags.incl(xhrfUploadComplete) if xhrfUploadListener in this.flags: @@ -260,7 +265,7 @@ proc onReadXHR(response: Response) = this.received.setLen(olen + n) if this.readyState == xhrsHeadersReceived: this.readyState = xhrsLoading - window.fireEvent(satReadystatechange, this) + window.fireReadyStateChangeEvent(this) window.fireProgressEvent(this, satProgress, int64(this.received.len), opaque.len) @@ -275,7 +280,7 @@ proc onFinishXHR(response: Response; success: bool) = window.fireProgressEvent(this, satProgress, recvLen, opaque.len) this.readyState = xhrsDone this.flags.excl(xhrfSend) - window.fireEvent(satReadystatechange, this) + window.fireReadyStateChangeEvent(this) window.fireProgressEvent(this, satLoad, recvLen, opaque.len) window.fireProgressEvent(this, satLoadend, recvLen, opaque.len) else: @@ -348,7 +353,7 @@ proc send(ctx: JSContext; this: XMLHttpRequest; body: JSValueConst = JS_NULL): let response = res.get this.response = response this.readyState = xhrsHeadersReceived - window.fireEvent(satReadystatechange, this) + window.fireReadyStateChangeEvent(this) if this.readyState != xhrsHeadersReceived: return let len = max(response.getContentLength(), 0) diff --git a/src/server/buffer.nim b/src/server/buffer.nim index 6f3e004f..1c3f9939 100644 --- a/src/server/buffer.nim +++ b/src/server/buffer.nim @@ -975,16 +975,14 @@ proc clone*(buffer: Buffer; newurl: URL): int {.proxy.} = proc dispatchDOMContentLoadedEvent(buffer: Buffer) = let window = buffer.window - let event = newEvent(satDOMContentLoaded.toAtom(), buffer.document) - event.isTrusted = true - window.fireEvent(event, buffer.document) + window.fireEvent(satDOMContentLoaded, buffer.document, bubbles = false, + cancelable = false, trusted = true) buffer.maybeReshape() proc dispatchLoadEvent(buffer: Buffer) = let window = buffer.window - let event = newEvent(satLoad.toAtom(), window) - event.isTrusted = true - window.fireEvent(event, window) + window.fireEvent(satLoad, window, bubbles = false, cancelable = false, + trusted = true) buffer.maybeReshape() proc finishLoad(buffer: Buffer; data: InputData): EmptyPromise = @@ -1312,13 +1310,21 @@ proc readSuccess*(buffer: Buffer; s: string; hasFd: bool): Request {.proxy.} = if buffer.config.scripting != smFalse: let window = buffer.window if input.inputType == itFile: - window.fireEvent(satInput, input) + window.fireEvent(satInput, input, bubbles = true, cancelable = true, + trusted = true) else: let inputEvent = newInputEvent(satInput.toAtom(), - InputEventInit(data: some(s), inputType: "insertText")) + InputEventInit( + data: some(s), + inputType: "insertText", + bubbles: true, + cancelable: true + ) + ) inputEvent.isTrusted = true window.fireEvent(inputEvent, input) - buffer.window.fireEvent(satChange, input) + buffer.window.fireEvent(satChange, input, bubbles = true, + cancelable = true, trusted = true) buffer.maybeReshape() return buffer.implicitSubmit(input) of TAG_TEXTAREA: @@ -1326,7 +1332,8 @@ proc readSuccess*(buffer: Buffer; s: string; hasFd: bool): Request {.proxy.} = textarea.value = s textarea.invalidate() if buffer.config.scripting != smFalse: - buffer.window.fireEvent(satChange, textarea) + buffer.window.fireEvent(satChange, textarea, bubbles = true, + cancelable = true, trusted = true) buffer.maybeReshape() else: discard return nil @@ -1398,7 +1405,8 @@ proc click(buffer: Buffer; option: HTMLOptionElement): ClickResult = if select.attrb(satMultiple): option.setSelected(not option.selected) if buffer.config.scripting != smFalse: - buffer.window.fireEvent(satChange, select) + buffer.window.fireEvent(satChange, select, bubbles = true, + cancelable = true, trusted = true) buffer.maybeReshape() return ClickResult() return buffer.click(select) @@ -1489,8 +1497,10 @@ proc click(buffer: Buffer; input: HTMLInputElement): ClickResult = input.setChecked(not input.checked) if buffer.config.scripting != smFalse: # Note: not an InputEvent. - buffer.window.fireEvent(satInput, input) - buffer.window.fireEvent(satChange, input) + buffer.window.fireEvent(satInput, input, bubbles = true, + cancelable = true, trusted = true) + buffer.window.fireEvent(satChange, input, bubbles = true, + cancelable = true, trusted = true) buffer.maybeReshape() return ClickResult() of itRadio: @@ -1498,8 +1508,10 @@ proc click(buffer: Buffer; input: HTMLInputElement): ClickResult = input.setChecked(true) if not wasChecked and buffer.config.scripting != smFalse: # See above. - buffer.window.fireEvent(satInput, input) - buffer.window.fireEvent(satChange, input) + buffer.window.fireEvent(satInput, input, bubbles = true, + cancelable = true, trusted = true) + buffer.window.fireEvent(satChange, input, bubbles = true, + cancelable = true, trusted = true) buffer.maybeReshape() return ClickResult() of itReset: @@ -1559,7 +1571,8 @@ proc click*(buffer: Buffer; cursorx, cursory: int): ClickResult {.proxy.} = let element = buffer.getCursorElement(cursorx, cursory) if element != nil: let window = buffer.window - let event = newEvent(satClick.toAtom(), element) + let event = newEvent(satClick.toAtom(), element, bubbles = true, + cancelable = true) event.isTrusted = true canceled = window.jsctx.dispatch(element, event) buffer.maybeReshape() @@ -1580,7 +1593,8 @@ proc select*(buffer: Buffer; selected: int): ClickResult {.proxy.} = if index != selected: select.setSelectedIndex(selected) if buffer.config.scripting != smFalse: - buffer.window.fireEvent(satChange, select) + buffer.window.fireEvent(satChange, select, bubbles = true, + cancelable = true, trusted = true) buffer.restoreFocus() buffer.maybeReshape() return ClickResult() diff --git a/test/net/xhr.html b/test/net/xhr.html index c9845d18..83a07d60 100644 --- a/test/net/xhr.html +++ b/test/net/xhr.html @@ -6,8 +6,11 @@ const x = new XMLHttpRequest(); assert(x.onreadystatechange === null); let changed = false; -function myFunction() { +function myFunction(event) { changed = true; + assert(!event.bubbles); + assert(!event.cancelable); + assert(event.isTrusted); } x.onreadystatechange = myFunction; assertEquals(myFunction, x.onreadystatechange); |