Skip to content

Commit 0870919

Browse files
committed
fix: crash and add once signal support in Watcher
1 parent 89e9b8e commit 0870919

File tree

9 files changed

+69
-39
lines changed

9 files changed

+69
-39
lines changed

api-documents/kit.watcher.addlistener.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<b>Signature:</b>
88

99
```typescript
10-
addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener<WatcherEvents<T>, K>): this;
10+
addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener<WatcherEvents<T>, K>, options?: AddEventListenerOptions): this;
1111
```
1212
1313
## Parameters
@@ -16,6 +16,7 @@ addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener<W
1616
| --- | --- | --- |
1717
| type | K | |
1818
| callback | EventListener&lt;[WatcherEvents](./kit.watcherevents.md)<!-- -->&lt;T&gt;, K&gt; | |
19+
| options | AddEventListenerOptions | <i>(Optional)</i> |
1920
2021
<b>Returns:</b>
2122

api-documents/kit.watcher.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export declare abstract class Watcher<T, Before extends Element, After extends E
4949
5050
| Method | Modifiers | Description |
5151
| --- | --- | --- |
52-
| [addListener(type, callback)](./kit.watcher.addlistener.md) | | |
52+
| [addListener(type, callback, options)](./kit.watcher.addlistener.md) | | |
5353
| [assignKeys(keyAssigner)](./kit.watcher.assignkeys.md) | | To help identify same nodes in different iteration, you need to implement a map function that map <code>node</code> to <code>key</code>If the key is changed, the same node will call through <code>forEachRemove</code> then <code>forEach</code> |
5454
| [defaultStarterForThen()](./kit.watcher.defaultstarterforthen.md) | | |
5555
| [dismissSingleModeWarning()](./kit.watcher.dismisssinglemodewarning.md) | | Dismiss the warning that let you enable single mode but the warning is false positive. |

api-documents/kit.webextensionmessage.eventregistry.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@
77
<b>Signature:</b>
88

99
```typescript
10-
protected get eventRegistry(): EventRegistry;
10+
protected get eventRegistry(): Emitter<any>;
1111
```

api-documents/kit.webextensionmessage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export declare class WebExtensionMessage<Message>
2222
| --- | --- | --- | --- |
2323
| [domain](./kit.webextensionmessage.domain.md) | | string | Same message name within different domain won't collide with each other. |
2424
| [enableLog](./kit.webextensionmessage.enablelog.md) | | boolean | |
25-
| [eventRegistry](./kit.webextensionmessage.eventregistry.md) | | EventRegistry | |
25+
| [eventRegistry](./kit.webextensionmessage.eventregistry.md) | | Emitter&lt;any&gt; | |
2626
| [events](./kit.webextensionmessage.events.md) | | { readonly \[K in keyof Message\]: [UnboundedRegistry](./kit.unboundedregistry.md)<!-- -->&lt;Message\[K\]&gt;; } | Event listeners |
2727
| [log](./kit.webextensionmessage.log.md) | | (...args: unknown\[\]) =&gt; void | |
2828
| [logFormatter](./kit.webextensionmessage.logformatter.md) | | (instance: this, key: string, data: unknown) =&gt; unknown\[\] | |

doc/holoflows-kit.api.report.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
/// <reference types="web-ext-types" />
88

99
import { Emitter } from '@servie/events';
10-
import { EventListener as EventListener_2 } from '@servie/events';
10+
import type { EventListener as EventListener_2 } from '@servie/events';
1111

1212
// @public
1313
export function assertEnvironment(env: Environment): void;
@@ -258,7 +258,7 @@ export class ValueRef<T> {
258258
export abstract class Watcher<T, Before extends Element, After extends Element, SingleMode extends boolean> implements PromiseLike<ResultOf<SingleMode, T>> {
259259
constructor(liveSelector: LiveSelector<T, SingleMode>);
260260
// (undocumented)
261-
addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener_2<WatcherEvents<T>, K>): this;
261+
addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener_2<WatcherEvents<T>, K>, options?: AddEventListenerOptions): this;
262262
assignKeys<Q = unknown>(keyAssigner: (node: T, index: number, arr: readonly T[]) => Q): this;
263263
// (undocumented)
264264
protected defaultStarterForThen(): void;
@@ -348,10 +348,8 @@ export class WebExtensionMessage<Message> {
348348
get domain(): string;
349349
// (undocumented)
350350
enableLog: boolean;
351-
// Warning: (ae-forgotten-export) The symbol "EventRegistry" needs to be exported by the entry point index.d.ts
352-
//
353351
// (undocumented)
354-
protected get eventRegistry(): EventRegistry;
352+
protected get eventRegistry(): Emitter<any>;
355353
get events(): {
356354
readonly [K in keyof Message]: UnboundedRegistry<Message[K]>;
357355
};

src/DOM/Watcher.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,21 @@
1010
* - Event watcher (based on addEventListener)
1111
*/
1212
import { DOMProxy, DOMProxyOptions } from './Proxy.js'
13-
import { Emitter, EventListener } from '@servie/events'
13+
import type { EventListener } from '@servie/events'
1414
import type { LiveSelector } from './LiveSelector.js'
1515

1616
import { Deadline, requestIdleCallback } from '../util/requestIdleCallback.js'
1717
import { isNil, uniqWith, intersectionWith, differenceWith } from 'lodash-es'
1818
import { timeout } from '../util/timeout.js'
19+
import { createEventTarget } from '../util/EventTarget.js'
1920

2021
/**
2122
* Use LiveSelector to watch dom change
2223
*/
2324
export abstract class Watcher<T, Before extends Element, After extends Element, SingleMode extends boolean>
2425
implements PromiseLike<ResultOf<SingleMode, T>>
2526
{
26-
private eventEmitter: Emitter<WatcherEvents<T>> = new Emitter()
27-
private removeListenerWeakMap = new Map<string, WeakMap<Function, Function>>()
27+
private events = createEventTarget<WatcherEvents<T>>()
2828
/**
2929
* The liveSelector that this object holds.
3030
*/
@@ -320,28 +320,28 @@ export abstract class Watcher<T, Before extends Element, After extends Element,
320320
this.lastKeyList = thisKeyList
321321
this.lastNodeList = currentIteration
322322

323-
if (this.eventEmitter.$.onIteration.size !== 0 && changedNodes.length + goneKeys.length + newKeys.length > 0) {
323+
if (this.events.has('onIteration') && changedNodes.length + goneKeys.length + newKeys.length > 0) {
324324
// Make a copy to prevent modifications
325325
const newMap = new Map<unknown, T>(newKeys.map((key) => [key, findFromNew(key)!]))
326326
const removedMap = new Map<unknown, T>(goneKeys.map((key) => [key, findFromLast(key)!]))
327327
const currentMap = new Map<unknown, T>(thisKeyList.map((key) => [key, findFromNew(key)!]))
328-
this.eventEmitter.emit('onIteration', {
328+
this.events.emit('onIteration', {
329329
new: newMap,
330330
removed: removedMap,
331331
current: currentMap,
332332
})
333333
}
334-
if (this.eventEmitter.$.onChange.size !== 0)
334+
if (this.events.has('onChange'))
335335
for (const [oldNode, newNode, oldKey, newKey] of changedNodes) {
336-
this.eventEmitter.emit('onChange', { oldValue: oldNode, newValue: newNode, oldKey, newKey })
336+
this.events.emit('onChange', { oldValue: oldNode, newValue: newNode, oldKey, newKey })
337337
}
338-
if (this.eventEmitter.$.onRemove.size !== 0)
338+
if (this.events.has('onRemove'))
339339
for (const key of goneKeys) {
340-
this.eventEmitter.emit('onRemove', { key, value: findFromLast(key)! })
340+
this.events.emit('onRemove', { key, value: findFromLast(key)! })
341341
}
342-
if (this.eventEmitter.$.onAdd.size !== 0)
342+
if (this.events.has('onAdd'))
343343
for (const key of newKeys) {
344-
this.eventEmitter.emit('onAdd', { key, value: findFromNew(key)! })
344+
this.events.emit('onAdd', { key, value: findFromNew(key)! })
345345
}
346346
// For firstDOMProxy
347347
const first = currentIteration[0]
@@ -392,7 +392,7 @@ export abstract class Watcher<T, Before extends Element, After extends Element,
392392
if (this.singleModeLastValue instanceof Node) {
393393
this._firstDOMProxy.realCurrent = null
394394
}
395-
this.eventEmitter.emit('onRemove', { key: undefined, value: this.singleModeLastValue! })
395+
this.events.emit('onRemove', { key: undefined, value: this.singleModeLastValue! })
396396
this.singleModeLastValue = undefined
397397
this.singleModeHasLastValue = false
398398
}
@@ -410,14 +410,14 @@ export abstract class Watcher<T, Before extends Element, After extends Element,
410410
applyUseForeachCallback(this.singleModeCallback)('warn mutation')(this._warning_mutation_)
411411
}
412412
}
413-
this.eventEmitter.emit('onAdd', { key: undefined, value: firstValue })
413+
this.events.emit('onAdd', { key: undefined, value: firstValue })
414414
this.singleModeLastValue = firstValue
415415
this.singleModeHasLastValue = true
416416
}
417417
// ? Case: value has changed
418418
else if (this.singleModeHasLastValue && !this.valueComparer(this.singleModeLastValue!, firstValue)) {
419419
applyUseForeachCallback(this.singleModeCallback)('target change')(firstValue, this.singleModeLastValue!)
420-
this.eventEmitter.emit('onChange', {
420+
this.events.emit('onChange', {
421421
newKey: undefined,
422422
oldKey: undefined,
423423
newValue: firstValue,
@@ -471,13 +471,16 @@ export abstract class Watcher<T, Before extends Element, After extends Element,
471471
//#endregion
472472
//#region events
473473

474-
addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener<WatcherEvents<T>, K>): this {
475-
if (!this.removeListenerWeakMap.has(type)) this.removeListenerWeakMap.set(type, new WeakMap())
476-
this.removeListenerWeakMap.get(type)!.set(callback, this.eventEmitter.on(type, callback))
474+
addListener<K extends keyof WatcherEvents<T>>(
475+
type: K,
476+
callback: EventListener<WatcherEvents<T>, K>,
477+
options?: AddEventListenerOptions,
478+
): this {
479+
this.events.add(type, callback, options)
477480
return this
478481
}
479482
removeListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener<WatcherEvents<T>, K>): this {
480-
this.removeListenerWeakMap.get(type)?.get(callback)?.()
483+
this.events.remove(type, callback)
481484
return this
482485
}
483486
//#endregion

src/DOM/Watchers/EventWatcher.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export class EventWatcher<
2727
this.requestIdleCallback(this.scheduleWatcherCheck, { timeout: 500 })
2828
}
2929
override startWatch(signal?: AbortSignal) {
30+
super.startWatch()
3031
signal?.addEventListener(
3132
'abort',
3233
() => {

src/Extension/MessageChannel.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/* eslint-disable no-bitwise */
33
import { Emitter } from '@servie/events'
44
import { EventIterator } from 'event-iterator'
5+
import { createEventTarget } from '../util/EventTarget.js'
56
import { Environment, getEnvironment, isEnvironment } from './Context.js'
67

78
/**
@@ -231,9 +232,9 @@ export class WebExtensionMessage<Message> {
231232
}
232233
public enableLog = false
233234
public log: (...args: unknown[]) => void = console.log
234-
#eventRegistry: EventRegistry = new Emitter<any>()
235+
#eventRegistry = createEventTarget<Record<string, [unknown]>>()
235236
protected get eventRegistry() {
236-
return this.#eventRegistry
237+
return this.#eventRegistry.emitter
237238
}
238239
}
239240
//#region Internal message handling
@@ -272,7 +273,7 @@ function shouldAcceptThisMessage(target: BoundTarget) {
272273
function UnboundedRegistry<T>(
273274
instance: WebExtensionMessage<T>,
274275
eventName: string,
275-
eventListener: Emitter<any>,
276+
eventListener: ReturnType<typeof createEventTarget<any>>,
276277
): UnboundedRegistry<T> {
277278
//#region Batch message
278279
let pausing = false
@@ -294,17 +295,11 @@ function UnboundedRegistry<T>(
294295
})
295296
}
296297
let binder: TargetBoundEventRegistry<T>
297-
const removeListenerWeakMap = new WeakMap<Function, Function>()
298298
function on(cb: (data: T) => void, options?: TargetBoundEventListenerOptions) {
299-
const off = eventListener.on(eventName, cb)
300-
removeListenerWeakMap.set(cb, off)
301-
302-
if (options?.once) eventListener.on(eventName, off)
303-
if (options?.signal) options.signal.addEventListener('abort', off, { once: true })
304-
return off
299+
return eventListener.add(eventName, cb, options)
305300
}
306301
function off(cb: (data: T) => void) {
307-
removeListenerWeakMap.get(cb)?.()
302+
eventListener.remove(eventName, cb)
308303
}
309304
function pause() {
310305
pausing = true
@@ -343,7 +338,6 @@ function UnboundedRegistry<T>(
343338
}
344339
return self
345340
}
346-
type EventRegistry = Emitter<Record<string, [unknown]>>
347341
type BoundTarget =
348342
| { kind: 'tab'; id: number }
349343
| { kind: 'target'; target: MessageTarget | Environment }

src/util/EventTarget.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Emitter, EventListener, once, ValidEventArgs } from '@servie/events'
2+
3+
/** @internal */
4+
export function createEventTarget<T = any>() {
5+
const emitter = new Emitter<T>()
6+
const offWeakMap = new Map<PropertyKey, WeakMap<Function, Function>>()
7+
function getOff(key: PropertyKey) {
8+
if (offWeakMap.has(key)) return offWeakMap.get(key)!
9+
const off = new WeakMap<Function, Function>()
10+
offWeakMap.set(key, off)
11+
return off
12+
}
13+
14+
return {
15+
has(key: keyof T) {
16+
return (emitter.$[key]?.size || 0) > 0
17+
},
18+
add<K extends keyof T>(event: K, callback: EventListener<T, K>, options?: AddEventListenerOptions) {
19+
const off = options?.once ? once(emitter, event, callback) : emitter.on(event, callback)
20+
21+
getOff(event).set(callback, off)
22+
options?.signal?.addEventListener('abort', off, { once: true })
23+
return off
24+
},
25+
remove<K extends keyof T>(event: K, callback: EventListener<T, K>) {
26+
getOff(event).get(callback)?.()
27+
},
28+
emitter,
29+
emit<K extends keyof T>(type: K, ...args: ValidEventArgs<T, K>) {
30+
emitter.emit(type, ...args)
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)