Skip to content

Commit

Permalink
feat(core): set listener stats
Browse files Browse the repository at this point in the history
  • Loading branch information
Tidyzq committed Sep 4, 2023
1 parent 1fcbc67 commit 128e542
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 49 deletions.
16 changes: 13 additions & 3 deletions glass-easel/src/backend/backend_protocol.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable class-methods-use-this */

import { EventBubbleStatus, EventOptions } from '../event'
import { EventBubbleStatus, EventOptions, MutLevel } from '../event'
import { safeCallback } from '../func_arr'
import {
BackendMode,
Expand Down Expand Up @@ -75,17 +75,19 @@ export interface Element extends Partial<suggestedBackend.Element> {
clearClasses(): void
setAttribute(name: string, value: unknown): void
removeAttribute(name: string): void
setDataset(name: string, value: unknown): void
setText(content: string): void
getBoundingClientRect(cb: (res: BoundingClientRect) => void): void
getScrollOffset(cb: (res: ScrollOffset) => void): void
setEventDefaultPrevented(type: string, enabled: boolean): void
setModelBindingStat(attributeName: string, listener: ((newValue: unknown) => void) | null): void
setListenerStats(type: string, capture: boolean, mutLevel: MutLevel): void
createIntersectionObserver(
relativeElement: Element | null,
relativeElementMargin: string,
thresholds: number[],
listener: (res: IntersectionStatus) => void,
): Observer
getContext(cb: (res: unknown) => void): void
}

export interface ShadowRootContext extends Element {
Expand Down Expand Up @@ -309,6 +311,10 @@ export class EmptyBackendElement implements Element {
// empty
}

setDataset(_name: string, _value: unknown): void {
// empty
}

setText(_content: string): void {
// empty
}
Expand All @@ -335,7 +341,7 @@ export class EmptyBackendElement implements Element {
}, 0)
}

setEventDefaultPrevented(_type: string, _enabled: boolean): void {
setListenerStats(_type: string, _capture: boolean, _mutLevel: MutLevel): void {
// empty
}

Expand All @@ -358,6 +364,10 @@ export class EmptyBackendElement implements Element {
},
}
}

getContext(cb: (res: unknown) => void): void {
cb(null)
}
}

/** A shadow root for empty backend implementation */
Expand Down
11 changes: 8 additions & 3 deletions glass-easel/src/backend/composed_backend_protocol.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable class-methods-use-this */

import { EventOptions, EventBubbleStatus } from '../event'
import { EventOptions, EventBubbleStatus, MutLevel } from '../event'
import { safeCallback } from '../func_arr'
import {
BackendMode,
Expand Down Expand Up @@ -63,14 +63,15 @@ export interface Element extends Partial<suggestedBackend.Element> {
setText(content: string): void
getBoundingClientRect(cb: (res: BoundingClientRect) => void): void
getScrollOffset(cb: (res: ScrollOffset) => void): void
setEventDefaultPrevented(type: string, enabled: boolean): void
setModelBindingStat(attributeName: string, listener: ((newValue: unknown) => void) | null): void
setListenerStats(type: string, capture: boolean, mutLevel: MutLevel): void
createIntersectionObserver(
relativeElement: Element | null,
relativeElementMargin: string,
thresholds: number[],
listener: (res: IntersectionStatus) => void,
): Observer
getContext(cb: (res: unknown) => void): void
}

/** An empty backend implementation */
Expand Down Expand Up @@ -275,7 +276,7 @@ export class EmptyComposedBackendElement implements Element {
}, 0)
}

setEventDefaultPrevented(_type: string, _enabled: boolean): void {
setListenerStats(_type: string, _capture: boolean, _mutLevel: MutLevel): void {
// empty
}

Expand All @@ -298,4 +299,8 @@ export class EmptyComposedBackendElement implements Element {
},
}
}

getContext(cb: (res: unknown) => void): void {
cb(null)
}
}
35 changes: 25 additions & 10 deletions glass-easel/src/backend/domlike_backend_protocol.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable class-methods-use-this */
/* global window, document */

import { EventOptions, EventBubbleStatus } from '../event'
import { EventOptions, EventBubbleStatus, MutLevel } from '../event'
import { safeCallback, triggerWarning } from '../func_arr'
import {
BackendMode,
Expand Down Expand Up @@ -39,7 +39,7 @@ export interface Context extends Partial<suggestedBackend.Context> {
options: EventOptions,
) => EventBubbleStatus,
): void
setElementEventDefaultPrevented(element: Element, type: string, enabled: boolean): void
setListenerStats(element: Element, type: string, capture: boolean, mutLevel: MutLevel): void
setModelBindingStat(
element: Element,
attributeName: string,
Expand All @@ -56,6 +56,7 @@ export interface Context extends Partial<suggestedBackend.Context> {
status: MediaQueryStatus,
listener: (res: { matches: boolean }) => void,
): Observer
getContext(element: Element, cb: (res: unknown) => void): void
}

export interface Element extends Partial<suggestedBackend.Element> {
Expand Down Expand Up @@ -120,6 +121,7 @@ export class CurrentWindowBackendContext implements Context {
private _$styleSheetRegistry = Object.create(null) as { [path: string]: string }
private _$delegatedEventListeners = Object.create(null) as Record<string, true>
private _$elementEventListeners = new WeakMap<Element, Record<string, true>>()
private _$elementCaptureEventListeners = new WeakMap<Element, Record<string, true>>()
private _$triggedEvents = new WeakSet<Event>()
private _$eventListener?: (
target: any,
Expand Down Expand Up @@ -213,12 +215,17 @@ export class CurrentWindowBackendContext implements Context {
}

private _$trigger(ev: Event, type: string, detail: unknown, bubbles: boolean, composed: boolean) {
if (!this._$eventListener) return
const target = ev.target
const bubbleStatus = this._$eventListener(target, type, detail, {
if (!this._$eventListener || !ev.target) return

let t: Element | null = ev.target as any as Element
while (t && !t.__wxElement) t = t.parentNode
if (!t) return

const bubbleStatus = this._$eventListener(t.__wxElement!, type, detail, {
originalEvent: ev,
bubbles,
composed,
capturePhase: true,
})
if (bubbleStatus === EventBubbleStatus.NoDefault) {
ev.preventDefault()
Expand Down Expand Up @@ -377,13 +384,14 @@ export class CurrentWindowBackendContext implements Context {
listeners.mouseup = true
}

setElementEventDefaultPrevented(element: Element, type: string, _enabled: boolean): void {
setListenerStats(element: Element, type: string, capture: boolean, mutLevel: MutLevel): void {
// for non-passive events,
// the default-prevented status can also be found in `EventBubbleStatus` ,
// so there is nothing to do with non-passive events.
if (!element) return

const shouldDelegate = DELEGATE_EVENTS.includes(type)
const defaultPrevented = mutLevel === MutLevel.Final

if (shouldDelegate) {
if (this._$delegatedEventListeners[type]) return
Expand All @@ -392,23 +400,26 @@ export class CurrentWindowBackendContext implements Context {
document.body.addEventListener(
type,
(ev) => {
if (defaultPrevented) ev.preventDefault()
this._$trigger(ev, type, this._$getEventDetail(ev), ev.bubbles, ev.composed)
},
{ capture: true },
)
return
}

if (!this._$elementEventListeners.has(element)) {
this._$elementEventListeners.set(element, Object.create(null))
}
const listeners = this._$elementEventListeners.get(element)!
const elementEventListeners = capture
? this._$elementCaptureEventListeners
: this._$elementEventListeners
if (!elementEventListeners.has(element)) elementEventListeners.set(element, Object.create(null))
const listeners = elementEventListeners.get(element)!
if (listeners[type]) return
listeners[type] = true

element.addEventListener(type, (ev) => {
if (this._$triggedEvents.has(ev)) return
this._$triggedEvents.add(ev)
if (defaultPrevented) ev.preventDefault()
this._$trigger(ev, type, this._$getEventDetail(ev), ev.bubbles, ev.composed)
})
}
Expand Down Expand Up @@ -542,4 +553,8 @@ export class CurrentWindowBackendContext implements Context {
},
}
}

getContext(element: Element, cb: (res: unknown) => void): void {
cb(null)
}
}
65 changes: 57 additions & 8 deletions glass-easel/src/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
EventOptions,
EventTarget,
FinalChanged,
MutLevel,
} from './event'
import { triggerWarning } from './func_arr'
import { ParsedSelector } from './selector'
Expand Down Expand Up @@ -1984,24 +1985,47 @@ export class Element implements NodeCast {
Event.dispatchEvent(this, ev)
}

private _$updateEventDefaultPrevented(name: string, enabled: boolean) {
if (!this._$backendElement) return
/* @internal */
private _$setListenerStats(
name: string,
finalChanged: FinalChanged,
options: EventListenerOptions = {},
) {
const capture = !!options.capture || !!options.useCapture
let mutLevel: MutLevel
switch (finalChanged) {
case FinalChanged.None:
mutLevel = MutLevel.None
break
case FinalChanged.Mut:
mutLevel = MutLevel.Mut
break
case FinalChanged.Final:
mutLevel = MutLevel.Final
break
default:
return
}
if (BM.DOMLIKE || (BM.DYNAMIC && this.getBackendMode() === BackendMode.Domlike)) {
;(this._$nodeTreeContext as domlikeBackend.Context).setElementEventDefaultPrevented(
;(this._$nodeTreeContext as domlikeBackend.Context).setListenerStats(
this._$backendElement as domlikeBackend.Element,
name,
enabled,
capture,
mutLevel,
)
} else {
;(this._$backendElement as backend.Element).setEventDefaultPrevented(name, enabled)
;(this._$backendElement as backend.Element | composedBackend.Element).setListenerStats(
name,
capture,
mutLevel,
)
}
}

/** Add an event listener on the element */
addListener(name: string, func: EventListener<unknown>, options?: EventListenerOptions) {
const finalChanged = this._$eventTarget.addListener(name, func, options)
if (finalChanged === FinalChanged.Init) this._$updateEventDefaultPrevented(name, false)
else if (finalChanged === FinalChanged.Added) this._$updateEventDefaultPrevented(name, true)
this._$setListenerStats(name, finalChanged, options)
if (this instanceof Component && this._$definition._$options.listenerChangeLifetimes) {
this.triggerLifetime('listenerChange', [true, name, func, options])
}
Expand All @@ -2011,7 +2035,7 @@ export class Element implements NodeCast {
removeListener(name: string, func: EventListener<unknown>, options?: EventListenerOptions) {
const finalChanged = this._$eventTarget.removeListener(name, func, options)
if (finalChanged === FinalChanged.Failed) return
if (finalChanged !== FinalChanged.NotChanged) this._$updateEventDefaultPrevented(name, false)
this._$setListenerStats(name, finalChanged, options)
if (this instanceof Component && this._$definition._$options.listenerChangeLifetimes) {
this.triggerLifetime('listenerChange', [false, name, func, options])
}
Expand Down Expand Up @@ -2067,6 +2091,15 @@ export class Element implements NodeCast {
if (this._$backendElement) this._$backendElement.removeAttribute(name)
}

/** Set a dataset on the element */
setDataset(name: string, value: unknown) {
this.dataset[name] = value

if (BM.SHADOW || (BM.DYNAMIC && this.getBackendMode() === BackendMode.Shadow)) {
;(this._$backendElement as backend.Element).setDataset(name, value)
}
}

/** Set a mark on the element */
setMark(name: string, value: unknown) {
if (!this._$marks) {
Expand Down Expand Up @@ -2649,4 +2682,20 @@ export class Element implements NodeCast {
}
return null
}

/**
* Get an interactive context
*/
getContext(cb: (res: unknown) => void): void {
const backendElement = this._$backendElement
if (!backendElement) return
if (BM.DOMLIKE || (BM.DYNAMIC && this.getBackendMode() === BackendMode.Domlike)) {
;(this._$nodeTreeContext as domlikeBackend.Context).getContext(
backendElement as domlikeBackend.Element,
cb,
)
} else {
;(backendElement as backend.Element).getContext(cb)
}
}
}
47 changes: 31 additions & 16 deletions glass-easel/src/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const getCurrentTimeStamp = () => {
return ts - relativeTimeStamp
}

const enum MutLevel {
export const enum MutLevel {
None = 0,
Mut = 1,
Final = 2,
Expand All @@ -70,9 +70,9 @@ type EventFuncArr<TDetail> = {
export const enum FinalChanged {
NotChanged = 0,
Failed,
Init,
Added,
Removed,
None,
Final,
Mut,
}

/** The target of an event */
Expand Down Expand Up @@ -120,14 +120,17 @@ export class EventTarget<TEvents extends { [type: string]: unknown }> {
}
}
efa.funcArr.add(func, mutLevel)
if (mutLevel === MutLevel.Final) efa.finalCount += 1
else if (mutLevel === MutLevel.Mut) efa.mutCount += 1
if (initialized) {
return mutLevel === MutLevel.Final && efa.finalCount === 1
? FinalChanged.Added
: FinalChanged.NotChanged
if (mutLevel === MutLevel.Final) {
efa.finalCount += 1
return initialized && efa.finalCount !== 1 ? FinalChanged.NotChanged : FinalChanged.Final
}
return mutLevel === MutLevel.Final ? FinalChanged.Added : FinalChanged.Init
if (mutLevel === MutLevel.Mut) {
efa.mutCount += 1
return initialized && !(efa.mutCount === 1 && efa.finalCount === 0)
? FinalChanged.NotChanged
: FinalChanged.Mut
}
return initialized ? FinalChanged.NotChanged : FinalChanged.None
}

removeListener<T extends string>(
Expand All @@ -141,11 +144,23 @@ export class EventTarget<TEvents extends { [type: string]: unknown }> {
if (!efa) return FinalChanged.Failed
const mutLevel = efa.funcArr.remove(func)
if (mutLevel === null) return FinalChanged.Failed
if (mutLevel === MutLevel.Final) efa.finalCount -= 1
else if (mutLevel === MutLevel.Mut) efa.mutCount -= 1
return mutLevel === MutLevel.Final && efa.finalCount === 0
? FinalChanged.Removed
: FinalChanged.NotChanged

if (mutLevel === MutLevel.Final) {
efa.finalCount -= 1
// eslint-disable-next-line no-nested-ternary
return efa.finalCount !== 0
? FinalChanged.NotChanged
: efa.mutCount > 0
? FinalChanged.Mut
: FinalChanged.None
}
if (mutLevel === MutLevel.Mut) {
efa.mutCount -= 1
return efa.mutCount !== 0 || efa.finalCount !== 0
? FinalChanged.NotChanged
: FinalChanged.None
}
return FinalChanged.NotChanged
}
}

Expand Down
Loading

0 comments on commit 128e542

Please sign in to comment.