如何跟踪和收集特定字段的所有已使用依赖项 JavaScript

How to trace and collect all used dependent JavaScript for a particular field

我想收集所有事件和 JavaScript 具有特定表单 field/all 表单字段(包括框架)依赖项的代码。我已经尝试使用 puppeteer 和 CDP 来获取某个字段的事件并随后收集 JavaScript。我能够成功获取事件详细信息。不确定如何遍历事件的所有痕迹以收集使用过的 JavaScript 代码。感谢快速帮助。

已使用以下代码收集事件。

listener = await windowHandle._client.send('DOMDebugger.getEventListeners', {
objectId: submitElementHandle._remoteObject.objectId
});

这是我能从 Chrome DevTools Protocol documents:

得到的所有信息
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();

try {
  const [page] = await browser.pages();

  const cdp = await page.target().createCDPSession();
  await cdp.send('Debugger.enable');

  const scriptIdToUrlMap = new Map();
  cdp.on('Debugger.scriptParsed', ({ scriptId, url }) => {
    scriptIdToUrlMap.set(scriptId, url);
  });

  await page.goto('https://chromedevtools.github.io/devtools-protocol/tot/DOMDebugger/');

  await page.waitForSelector('body > main');
  const { objectId } = (await cdp.send('Runtime.evaluate', {
    expression: 'document.querySelector("body > main")',
  })).result;

  const { listeners } = await cdp.send('DOMDebugger.getEventListeners', { objectId });

  for (const listener of listeners) {
    console.log('Listener details:', listener, '\n');

    console.log('Script URL:', scriptIdToUrlMap.get(listener.scriptId), '\n');

    const { scriptSource } = await cdp.send('Debugger.getScriptSource', {
      scriptId: listener.scriptId,
    });
    console.log(
      'Script source start:',
      scriptSource.split('\n')[listener.lineNumber].slice(listener.columnNumber),
      '...\n',
    );
  }
} catch (err) { console.error(err); } finally { await browser.close(); }

当前输出:

Listener details: {
  type: 'click',
  useCapture: false,
  passive: false,
  once: false,
  scriptId: '4',
  lineNumber: 181,
  columnNumber: 649
}

Script URL: https://chromedevtools.github.io/devtools-protocol/scripts/index.js

Script source start: (){I.classList.contains("shown")&&(I.classList.remove("shown"),P.focus())}document.addEventListener("keydown",e=>{e.metaKey||e.ctrlKey||e.altKey||(e.keyCode>=65&&e.keyCode<=90&&document.querySelector("cr-search-control").inputElement.focus(),"Escape"===e.key&&I.classList.contains("shown")&&I.classList.remove("shown"))}),P.addEventListener("click",e=>{e.stopPropagation(),I.addEventListener("transitionend",()=>{O.focus()},{once:!0}),I.classList.add("shown")}),B.addEventListener("click",W),O.addEventListener("click",W); ...

另一种变体,更老套,更不可靠。

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch(/* { headless: false, defaultViewport: null } */);

try {
  const [page] = await browser.pages();

  // Hook on setting listeners.
  await page.evaluateOnNewDocument(() => {
    window._puppeteerListenersMap = new Map();
    const _puppeteerOldAddEventListener = EventTarget.prototype.addEventListener;

    EventTarget.prototype.addEventListener = function newAddEventListener(...args) {
      const element = this;
      const [eventName, eventListener] = args;

      if (!_puppeteerListenersMap.get(element)) {
        _puppeteerListenersMap.set(element, Object.create(null));
      }
      const allListeners = _puppeteerListenersMap.get(element);
      allListeners[eventName] ??= [];
      allListeners[eventName].push(eventListener);

      _puppeteerOldAddEventListener.call(this, ...args);
    };
  });

  await page.goto('https://example.org/');

  // Test setting listeners.
  await page.evaluate(() => {
    document.body.addEventListener('click', function click() {
      console.log('addEventListener click');
    });
    document.body.addEventListener('dblclick', function dblclick() {
      console.log('addEventListener dblclick');
    });
    document.body.onclick = function onclick() { console.log('onclick'); };
    document.body.ondblclick = function ondblclick() { console.log('ondblclick'); };
  });

  // Test getting listeners.
  const data = await page.evaluate(() => {
    const element = document.body;
    const elementListeners = Object.create(null);

    const allListeners = _puppeteerListenersMap.get(element);
    if (allListeners) {
      for (const [eventName, eventListeners] of Object.entries(allListeners)) {
        elementListeners[eventName] = eventListeners.map(
          eventListener => eventListener.toString()
        );
      }
    }

    for (const name in element) {
      if (name.startsWith('on') &&
          element[name] !== null &&
          typeof element[name] === 'function'
      ) {
        elementListeners[name] = element[name].toString();
      }
    }

    return elementListeners;
  });
  console.log(JSON.stringify(data, null, '  '));
} catch (err) { console.error(err); } finally { await browser.close(); }

输出:

{
  "click": [
    "function click() {\n      console.log('addEventListener click');\n    }"
  ],
  "dblclick": [
    "function dblclick() {\n      console.log('addEventListener dblclick');\n    }"
  ],
  "onclick": "function onclick() { console.log('onclick'); }",
  "ondblclick": "function ondblclick() { console.log('ondblclick'); }"
}

更新。从 codepen 测试页面:

  // Test getting listeners.
  const data = await page.evaluate(() => {
    const element = document.querySelector('#signup_v1-email');
    element.value = 'foo'; // Input invalid email.

    const elementListeners = Object.create(null);

    const allListeners = _puppeteerListenersMap.get(element);
    if (allListeners) {
      for (const [eventName, eventListeners] of Object.entries(allListeners)) {
        elementListeners[eventName] = eventListeners.map(
          eventListener => (
            eventListener.call(element, new Event(eventName)), // 'not a valid email' in Browser console.
            eventListener.toString()
          )
        );
      }
    }

    for (const name in element) {
      if (name.startsWith('on') &&
          element[name] !== null &&
          typeof element[name] === 'function'
      ) {
        elementListeners[name] = element[name].toString();
      }
    }

    return elementListeners;
  });