You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求支持ListenForWindowEvent/PlayWindowEvent的Chrome事件录制扩展

Solution: Chrome Extension with Low-Level Event Recording/Playback

I get exactly what you're dealing with—regular DOM event listeners and synthetic events fall flat when it comes to system-level UI components like scrollbars, native <select> dropdowns, or even some input field interactions. The issue is that these components are handled by the browser's native code, not the page's DOM, so your regular JS can't capture or replicate the low-level mouse/keyboard events that drive them.

Here's a Chrome Extension implementation that exposes the ListenForWindowEvent(), PlayWindowEvent(), and a helper StopWindowEventRecording() function directly to your page's JS. It uses Chrome's debugger API to bypass the DOM sandbox and interact with the browser's core input system.

Extension Setup

1. Manifest File (manifest.json)

This defines the extension's permissions and structure. We need the debugger permission to access low-level events, and activeTab to work on any page you activate:

{
  "manifest_version": 3,
  "name": "Native Window Event Recorder",
  "version": "1.0",
  "permissions": ["debugger", "activeTab"],
  "background": {
    "service_worker": "background.js"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ]
}

2. Background Service Worker (background.js)

This handles the heavy lifting: connecting to the browser debugger, recording events, and playing them back. It listens for messages from the content script to control recording/playback.

let recordedEvents = [];
let isRecording = false;

// Listen for debugger events (low-level input actions)
chrome.debugger.onEvent.addListener((source, method, params) => {
  if (!isRecording) return;
  // Only track input-related events (mouse/keyboard)
  if (method.startsWith('Input.')) {
    recordedEvents.push({
      method,
      params,
      timestamp: Date.now()
    });
  }
});

// Handle messages from the content script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  switch (message.action) {
    case 'startRecording':
      isRecording = true;
      recordedEvents = [];
      // Attach debugger to the current tab
      chrome.debugger.attach({ tabId: sender.tab.id }, '1.3', () => {
        if (chrome.runtime.lastError) {
          sendResponse({ success: false, error: chrome.runtime.lastError.message });
          return;
        }
        // Enable input event monitoring
        chrome.debugger.sendCommand({ tabId: sender.tab.id }, 'Input.enable', {}, () => {
          sendResponse({ success: true });
        });
      });
      return true; // Keep response channel open for async operation

    case 'stopRecording':
      isRecording = false;
      // Disable input monitoring and detach debugger
      chrome.debugger.sendCommand({ tabId: sender.tab.id }, 'Input.disable', {}, () => {
        chrome.debugger.detach({ tabId: sender.tab.id });
        sendResponse({ success: true, events: recordedEvents });
      });
      return true;

    case 'playEvents':
      const events = message.events;
      let currentIndex = 0;

      const playNext = () => {
        if (currentIndex >= events.length) {
          chrome.debugger.detach({ tabId: sender.tab.id });
          sendResponse({ success: true });
          return;
        }

        const event = events[currentIndex];
        // Calculate delay to match original timing between events
        const delay = currentIndex > 0 ? event.timestamp - events[currentIndex - 1].timestamp : 0;

        setTimeout(() => {
          // Dispatch the low-level event via debugger
          chrome.debugger.sendCommand({ tabId: sender.tab.id }, event.method, event.params, () => {
            currentIndex++;
            playNext();
          });
        }, delay);
      };

      // Attach debugger and start playback
      chrome.debugger.attach({ tabId: sender.tab.id }, '1.3', () => {
        if (chrome.runtime.lastError) {
          sendResponse({ success: false, error: chrome.runtime.lastError.message });
          return;
        }
        chrome.debugger.sendCommand({ tabId: sender.tab.id }, 'Input.enable', {}, playNext);
      });
      return true;
  }
});

3. Content Script (content.js)

This injects the required functions into the page's global scope, so your own JS can call them directly:

// Start recording low-level window events
window.ListenForWindowEvent = async function() {
  return new Promise((resolve, reject) => {
    chrome.runtime.sendMessage({ action: 'startRecording' }, (response) => {
      response.success ? resolve('Recording initiated') : reject(new Error(response.error));
    });
  });
};

// Play back recorded events
window.PlayWindowEvent = async function(recordedEvents) {
  return new Promise((resolve, reject) => {
    chrome.runtime.sendMessage({ action: 'playEvents', events: recordedEvents }, (response) => {
      response.success ? resolve('Playback completed') : reject(new Error(response.error));
    });
  });
};

// Helper to stop recording and retrieve events
window.StopWindowEventRecording = async function() {
  return new Promise((resolve, reject) => {
    chrome.runtime.sendMessage({ action: 'stopRecording' }, (response) => {
      response.success ? resolve(response.events) : reject(new Error(response.error));
    });
  });
};

How to Use in Your Own Code

Once the extension is loaded, you can call these functions from any script on the page:

// Example workflow
async function demoRecording() {
  // Start recording
  await ListenForWindowEvent();
  
  // Perform actions: drag scrollbars, select dropdown options, type in inputs, etc.
  
  // Stop recording and get the event array
  const events = await StopWindowEventRecording();
  
  // Wait a second, then play back
  setTimeout(async () => {
    await PlayWindowEvent(events);
  }, 1000);
}

// Run the demo
demoRecording();

Why This Works

  • Low-Level Access: The chrome.debugger API talks directly to the browser's input system, capturing events that never reach the page's DOM (like dragging a native scrollbar or selecting a <select> option via mouse).
  • Accurate Playback: Instead of triggering synthetic DOM events, we use Input.dispatchMouseEvent and Input.dispatchKeyEvent to replicate the exact user input, so system components behave exactly as they did during recording.

Notes

  • The first time you use the extension, Chrome will prompt you to grant debugger permissions—you'll need to approve this.
  • You can't use Chrome's DevTools debugger on the same tab while this extension is recording/playing back (they'll conflict).
  • This covers most mouse and keyboard interactions, but if you need to capture other system events (like touch), you can extend the event filtering in background.js to include Input.touch* methods.

内容的提问来源于stack exchange,提问作者M Katz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 03:53:01