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

Viewed 367

I would like to gather all events and JavaScript code with dependencies for a particular form field/all form field including frameworks. I have tried with puppeteer and CDP to get the events for a field and subsequently gather JavaScript. I was able to get events details successfully. Not sure how to walk all traces from the event to collect used JavaScript code. Quick help appreciated.

Following code has been used to collect events.

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

Another variant, more hacky and less reliable.

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(); }

Output:

{
  "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'); }"
}

UPD. Testing for the page from 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;
  });

This is all I can get from the 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(); }

Current output:

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); ...
Related