page.on()-style event extension for CDP events | 能否添加page.on 功能

Author: wuge-1337Created May 15, 2026Updated Sep 8, 2026

类似playwright的page.on。

`

class PageEvent: def init(self, page): self.page = page self.driver = page._driver self.listeners = {} self._original_handlers = {} self._queue_hooked = False self._hook_event_queue()

def _hook_event_queue(self):
    if self._queue_hooked: return
    self._queue_hooked = True
    original_put = self.driver.event_queue.put
    def patched_put(msg):
        sid = msg.get('sessionId')
        method = msg.get('method', '')
        if sid:
            # print(f"[queue] listeners for {method}: {len(self.listeners.get(method, []))}")
            params = msg.get('params', {})
            for cb in list(self.listeners.get(method, [])):
                try:
                    cb({"type": method, "sessionId": sid, "data": params})
                except Exception as e:
                    print(f"[PageEvent] 子Session回调错误 ({method}): {e}")
        original_put(msg)

    self.driver.event_queue.put = patched_put

def run_cdp_session(self, method, session_id, **params):
    msg = {
        "sessionId": session_id,  # <- Flat Session 关键字段
        "method": method,
        "params": params or {}
    }
    self.driver._send(msg)  # _send 会自动加 id 并发出

def on(self, event_name, callback):
    self.listeners.setdefault(event_name, []).append(callback)
    # 如果还没 hook,就 hook
    if event_name not in self._original_handlers:
        self._hook(event_name)
    return callback  # 方便 off 用

def once(self, event_name, callback):
    def wrapper(event):
        try:
            callback(event)
        finally:
            self.off(event_name, wrapper)
    return self.on(event_name, wrapper)

def off(self, event_name, callback=None):
    if event_name not in self.listeners:  return

    if callback is None:
        self.listeners[event_name].clear()
    else:
        try:
            self.listeners[event_name].remove(callback)
        except ValueError:  pass

    # 如果已经没有 listener -> 恢复原 handler
    if not self.listeners[event_name]:  self._unhook(event_name)


def _hook(self, event_name):
    # 保存原 handler
    original = self.driver.event_handlers.get(event_name)
    self._original_handlers[event_name] = original

    def dispatcher(**params):
        # 执行监听器(拷贝避免修改时崩)
        for cb in list(self.listeners.get(event_name, [])):
            try:
                cb({"type": event_name, "data": params})
            except Exception as e:
                print(f"[PageEvent] 回调错误 ({event_name}): {e}")

        # 动态获取 original(避免捕获旧引用)
        current_original = self._original_handlers.get(event_name)
        if current_original:
            try:
                current_original(**params)
            except Exception as e:
                print(f"[PageEvent] 原 handler 错误 ({event_name}): {e}")
    # 替换 handler
    self.driver.event_handlers[event_name] = dispatcher


def _unhook(self, event_name):
    original = self._original_handlers.get(event_name)
    if original:
        self.driver.event_handlers[event_name] = original
    else:
        # 原本没有 handler -> 删除
        self.driver.event_handlers.pop(event_name, None)
    # 清理
    self._original_handlers.pop(event_name, None)
    self.listeners.pop(event_name, None)

def listener_count(self, event_name):
    return len(self.listeners.get(event_name, []))

`