diff --git a/api-documents/kit.watcher.addlistener.md b/api-documents/kit.watcher.addlistener.md
index e226b51..60b5752 100644
--- a/api-documents/kit.watcher.addlistener.md
+++ b/api-documents/kit.watcher.addlistener.md
@@ -7,7 +7,7 @@
 <b>Signature:</b>
 
 ```typescript
-addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener<WatcherEvents<T>, K>): this;
+addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener<WatcherEvents<T>, K>, options?: AddEventListenerOptions): this;
 ```
 
 ## Parameters
@@ -16,6 +16,7 @@ addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener<W
 |  --- | --- | --- |
 |  type | K |  |
 |  callback | EventListener&lt;[WatcherEvents](./kit.watcherevents.md)<!-- -->&lt;T&gt;, K&gt; |  |
+|  options | AddEventListenerOptions | <i>(Optional)</i> |
 
 <b>Returns:</b>
 
diff --git a/api-documents/kit.watcher.md b/api-documents/kit.watcher.md
index 30965b4..59c4cd2 100644
--- a/api-documents/kit.watcher.md
+++ b/api-documents/kit.watcher.md
@@ -49,7 +49,7 @@ export declare abstract class Watcher<T, Before extends Element, After extends E
 
 |  Method | Modifiers | Description |
 |  --- | --- | --- |
-|  [addListener(type, callback)](./kit.watcher.addlistener.md) |  |  |
+|  [addListener(type, callback, options)](./kit.watcher.addlistener.md) |  |  |
 |  [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> |
 |  [defaultStarterForThen()](./kit.watcher.defaultstarterforthen.md) |  |  |
 |  [dismissSingleModeWarning()](./kit.watcher.dismisssinglemodewarning.md) |  | Dismiss the warning that let you enable single mode but the warning is false positive. |
diff --git a/api-documents/kit.webextensionmessage.eventregistry.md b/api-documents/kit.webextensionmessage.eventregistry.md
index d749d5e..daab9fd 100644
--- a/api-documents/kit.webextensionmessage.eventregistry.md
+++ b/api-documents/kit.webextensionmessage.eventregistry.md
@@ -7,5 +7,5 @@
 <b>Signature:</b>
 
 ```typescript
-protected get eventRegistry(): EventRegistry;
+protected get eventRegistry(): Emitter<any>;
 ```
diff --git a/api-documents/kit.webextensionmessage.md b/api-documents/kit.webextensionmessage.md
index 97304d4..9892f7a 100644
--- a/api-documents/kit.webextensionmessage.md
+++ b/api-documents/kit.webextensionmessage.md
@@ -22,7 +22,7 @@ export declare class WebExtensionMessage<Message>
 |  --- | --- | --- | --- |
 |  [domain](./kit.webextensionmessage.domain.md) |  | string | Same message name within different domain won't collide with each other. |
 |  [enableLog](./kit.webextensionmessage.enablelog.md) |  | boolean |  |
-|  [eventRegistry](./kit.webextensionmessage.eventregistry.md) |  | EventRegistry |  |
+|  [eventRegistry](./kit.webextensionmessage.eventregistry.md) |  | Emitter&lt;any&gt; |  |
 |  [events](./kit.webextensionmessage.events.md) |  | { readonly \[K in keyof Message\]: [UnboundedRegistry](./kit.unboundedregistry.md)<!-- -->&lt;Message\[K\]&gt;; } | Event listeners |
 |  [log](./kit.webextensionmessage.log.md) |  | (...args: unknown\[\]) =&gt; void |  |
 |  [logFormatter](./kit.webextensionmessage.logformatter.md) |  | (instance: this, key: string, data: unknown) =&gt; unknown\[\] |  |
diff --git a/doc/holoflows-kit.api.report.md b/doc/holoflows-kit.api.report.md
index ddf1d53..ccd41a9 100644
--- a/doc/holoflows-kit.api.report.md
+++ b/doc/holoflows-kit.api.report.md
@@ -7,7 +7,7 @@
 /// <reference types="web-ext-types" />
 
 import { Emitter } from '@servie/events';
-import { EventListener as EventListener_2 } from '@servie/events';
+import type { EventListener as EventListener_2 } from '@servie/events';
 
 // @public
 export function assertEnvironment(env: Environment): void;
@@ -258,7 +258,7 @@ export class ValueRef<T> {
 export abstract class Watcher<T, Before extends Element, After extends Element, SingleMode extends boolean> implements PromiseLike<ResultOf<SingleMode, T>> {
     constructor(liveSelector: LiveSelector<T, SingleMode>);
     // (undocumented)
-    addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener_2<WatcherEvents<T>, K>): this;
+    addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener_2<WatcherEvents<T>, K>, options?: AddEventListenerOptions): this;
     assignKeys<Q = unknown>(keyAssigner: (node: T, index: number, arr: readonly T[]) => Q): this;
     // (undocumented)
     protected defaultStarterForThen(): void;
@@ -348,10 +348,8 @@ export class WebExtensionMessage<Message> {
     get domain(): string;
     // (undocumented)
     enableLog: boolean;
-    // Warning: (ae-forgotten-export) The symbol "EventRegistry" needs to be exported by the entry point index.d.ts
-    //
     // (undocumented)
-    protected get eventRegistry(): EventRegistry;
+    protected get eventRegistry(): Emitter<any>;
     get events(): {
         readonly [K in keyof Message]: UnboundedRegistry<Message[K]>;
     };
diff --git a/src/DOM/Watcher.ts b/src/DOM/Watcher.ts
index 3f16059..10f4d61 100644
--- a/src/DOM/Watcher.ts
+++ b/src/DOM/Watcher.ts
@@ -10,12 +10,13 @@
  * - Event watcher (based on addEventListener)
  */
 import { DOMProxy, DOMProxyOptions } from './Proxy.js'
-import { Emitter, EventListener } from '@servie/events'
+import type { EventListener } from '@servie/events'
 import type { LiveSelector } from './LiveSelector.js'
 
 import { Deadline, requestIdleCallback } from '../util/requestIdleCallback.js'
 import { isNil, uniqWith, intersectionWith, differenceWith } from 'lodash-es'
 import { timeout } from '../util/timeout.js'
+import { createEventTarget } from '../util/EventTarget.js'
 
 /**
  * Use LiveSelector to watch dom change
@@ -23,8 +24,7 @@ import { timeout } from '../util/timeout.js'
 export abstract class Watcher<T, Before extends Element, After extends Element, SingleMode extends boolean>
     implements PromiseLike<ResultOf<SingleMode, T>>
 {
-    private eventEmitter: Emitter<WatcherEvents<T>> = new Emitter()
-    private removeListenerWeakMap = new Map<string, WeakMap<Function, Function>>()
+    private events = createEventTarget<WatcherEvents<T>>()
     /**
      * The liveSelector that this object holds.
      */
@@ -320,28 +320,28 @@ export abstract class Watcher<T, Before extends Element, After extends Element,
         this.lastKeyList = thisKeyList
         this.lastNodeList = currentIteration
 
-        if (this.eventEmitter.$.onIteration.size !== 0 && changedNodes.length + goneKeys.length + newKeys.length > 0) {
+        if (this.events.has('onIteration') && changedNodes.length + goneKeys.length + newKeys.length > 0) {
             // Make a copy to prevent modifications
             const newMap = new Map<unknown, T>(newKeys.map((key) => [key, findFromNew(key)!]))
             const removedMap = new Map<unknown, T>(goneKeys.map((key) => [key, findFromLast(key)!]))
             const currentMap = new Map<unknown, T>(thisKeyList.map((key) => [key, findFromNew(key)!]))
-            this.eventEmitter.emit('onIteration', {
+            this.events.emit('onIteration', {
                 new: newMap,
                 removed: removedMap,
                 current: currentMap,
             })
         }
-        if (this.eventEmitter.$.onChange.size !== 0)
+        if (this.events.has('onChange'))
             for (const [oldNode, newNode, oldKey, newKey] of changedNodes) {
-                this.eventEmitter.emit('onChange', { oldValue: oldNode, newValue: newNode, oldKey, newKey })
+                this.events.emit('onChange', { oldValue: oldNode, newValue: newNode, oldKey, newKey })
             }
-        if (this.eventEmitter.$.onRemove.size !== 0)
+        if (this.events.has('onRemove'))
             for (const key of goneKeys) {
-                this.eventEmitter.emit('onRemove', { key, value: findFromLast(key)! })
+                this.events.emit('onRemove', { key, value: findFromLast(key)! })
             }
-        if (this.eventEmitter.$.onAdd.size !== 0)
+        if (this.events.has('onAdd'))
             for (const key of newKeys) {
-                this.eventEmitter.emit('onAdd', { key, value: findFromNew(key)! })
+                this.events.emit('onAdd', { key, value: findFromNew(key)! })
             }
         // For firstDOMProxy
         const first = currentIteration[0]
@@ -392,7 +392,7 @@ export abstract class Watcher<T, Before extends Element, After extends Element,
             if (this.singleModeLastValue instanceof Node) {
                 this._firstDOMProxy.realCurrent = null
             }
-            this.eventEmitter.emit('onRemove', { key: undefined, value: this.singleModeLastValue! })
+            this.events.emit('onRemove', { key: undefined, value: this.singleModeLastValue! })
             this.singleModeLastValue = undefined
             this.singleModeHasLastValue = false
         }
@@ -410,14 +410,14 @@ export abstract class Watcher<T, Before extends Element, After extends Element,
                     applyUseForeachCallback(this.singleModeCallback)('warn mutation')(this._warning_mutation_)
                 }
             }
-            this.eventEmitter.emit('onAdd', { key: undefined, value: firstValue })
+            this.events.emit('onAdd', { key: undefined, value: firstValue })
             this.singleModeLastValue = firstValue
             this.singleModeHasLastValue = true
         }
         // ? Case: value has changed
         else if (this.singleModeHasLastValue && !this.valueComparer(this.singleModeLastValue!, firstValue)) {
             applyUseForeachCallback(this.singleModeCallback)('target change')(firstValue, this.singleModeLastValue!)
-            this.eventEmitter.emit('onChange', {
+            this.events.emit('onChange', {
                 newKey: undefined,
                 oldKey: undefined,
                 newValue: firstValue,
@@ -471,13 +471,16 @@ export abstract class Watcher<T, Before extends Element, After extends Element,
     //#endregion
     //#region events
 
-    addListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener<WatcherEvents<T>, K>): this {
-        if (!this.removeListenerWeakMap.has(type)) this.removeListenerWeakMap.set(type, new WeakMap())
-        this.removeListenerWeakMap.get(type)!.set(callback, this.eventEmitter.on(type, callback))
+    addListener<K extends keyof WatcherEvents<T>>(
+        type: K,
+        callback: EventListener<WatcherEvents<T>, K>,
+        options?: AddEventListenerOptions,
+    ): this {
+        this.events.add(type, callback, options)
         return this
     }
     removeListener<K extends keyof WatcherEvents<T>>(type: K, callback: EventListener<WatcherEvents<T>, K>): this {
-        this.removeListenerWeakMap.get(type)?.get(callback)?.()
+        this.events.remove(type, callback)
         return this
     }
     //#endregion
diff --git a/src/DOM/Watchers/EventWatcher.ts b/src/DOM/Watchers/EventWatcher.ts
index c4ccfc9..8a3810c 100644
--- a/src/DOM/Watchers/EventWatcher.ts
+++ b/src/DOM/Watchers/EventWatcher.ts
@@ -27,6 +27,7 @@ export class EventWatcher<
         this.requestIdleCallback(this.scheduleWatcherCheck, { timeout: 500 })
     }
     override startWatch(signal?: AbortSignal) {
+        super.startWatch()
         signal?.addEventListener(
             'abort',
             () => {
diff --git a/src/Extension/MessageChannel.ts b/src/Extension/MessageChannel.ts
index d39ebc3..e9de14d 100644
--- a/src/Extension/MessageChannel.ts
+++ b/src/Extension/MessageChannel.ts
@@ -2,6 +2,7 @@
 /* eslint-disable no-bitwise */
 import { Emitter } from '@servie/events'
 import { EventIterator } from 'event-iterator'
+import { createEventTarget } from '../util/EventTarget.js'
 import { Environment, getEnvironment, isEnvironment } from './Context.js'
 
 /**
@@ -231,9 +232,9 @@ export class WebExtensionMessage<Message> {
     }
     public enableLog = false
     public log: (...args: unknown[]) => void = console.log
-    #eventRegistry: EventRegistry = new Emitter<any>()
+    #eventRegistry = createEventTarget<Record<string, [unknown]>>()
     protected get eventRegistry() {
-        return this.#eventRegistry
+        return this.#eventRegistry.emitter
     }
 }
 //#region Internal message handling
@@ -272,7 +273,7 @@ function shouldAcceptThisMessage(target: BoundTarget) {
 function UnboundedRegistry<T>(
     instance: WebExtensionMessage<T>,
     eventName: string,
-    eventListener: Emitter<any>,
+    eventListener: ReturnType<typeof createEventTarget<any>>,
 ): UnboundedRegistry<T> {
     //#region Batch message
     let pausing = false
@@ -294,17 +295,11 @@ function UnboundedRegistry<T>(
         })
     }
     let binder: TargetBoundEventRegistry<T>
-    const removeListenerWeakMap = new WeakMap<Function, Function>()
     function on(cb: (data: T) => void, options?: TargetBoundEventListenerOptions) {
-        const off = eventListener.on(eventName, cb)
-        removeListenerWeakMap.set(cb, off)
-
-        if (options?.once) eventListener.on(eventName, off)
-        if (options?.signal) options.signal.addEventListener('abort', off, { once: true })
-        return off
+        return eventListener.add(eventName, cb, options)
     }
     function off(cb: (data: T) => void) {
-        removeListenerWeakMap.get(cb)?.()
+        eventListener.remove(eventName, cb)
     }
     function pause() {
         pausing = true
@@ -343,7 +338,6 @@ function UnboundedRegistry<T>(
     }
     return self
 }
-type EventRegistry = Emitter<Record<string, [unknown]>>
 type BoundTarget =
     | { kind: 'tab'; id: number }
     | { kind: 'target'; target: MessageTarget | Environment }
diff --git a/src/util/EventTarget.ts b/src/util/EventTarget.ts
new file mode 100644
index 0000000..e8ae891
--- /dev/null
+++ b/src/util/EventTarget.ts
@@ -0,0 +1,33 @@
+import { Emitter, EventListener, once, ValidEventArgs } from '@servie/events'
+
+/** @internal */
+export function createEventTarget<T = any>() {
+    const emitter = new Emitter<T>()
+    const offWeakMap = new Map<PropertyKey, WeakMap<Function, Function>>()
+    function getOff(key: PropertyKey) {
+        if (offWeakMap.has(key)) return offWeakMap.get(key)!
+        const off = new WeakMap<Function, Function>()
+        offWeakMap.set(key, off)
+        return off
+    }
+
+    return {
+        has(key: keyof T) {
+            return (emitter.$[key]?.size || 0) > 0
+        },
+        add<K extends keyof T>(event: K, callback: EventListener<T, K>, options?: AddEventListenerOptions) {
+            const off = options?.once ? once(emitter, event, callback) : emitter.on(event, callback)
+
+            getOff(event).set(callback, off)
+            options?.signal?.addEventListener('abort', off, { once: true })
+            return off
+        },
+        remove<K extends keyof T>(event: K, callback: EventListener<T, K>) {
+            getOff(event).get(callback)?.()
+        },
+        emitter,
+        emit<K extends keyof T>(type: K, ...args: ValidEventArgs<T, K>) {
+            emitter.emit(type, ...args)
+        }
+    }
+}