Scriptlets icon indicating copy to clipboard operation
Scriptlets copied to clipboard

Improve 'prevent-xhr' — add missed events

Open AdamWr opened this issue 1 year ago • 0 comments

Related to https://github.com/AdguardTeam/AdguardFilters/issues/175034 and https://github.com/AdguardTeam/AdguardFilters/issues/174876

These websites use onreadystatechange to check if 4 events were fired and #%#//scriptlet('prevent-xhr', 'pagead2.googlesyndication.com') doesn't work because currently only 2 events are invoked.

Steps to reproduce:

  1. Add this rule:
example.org#%#//scriptlet('prevent-xhr', 'pagead2.googlesyndication.com')
  1. Go to - https://example.org/
  2. In browser console run:
(() => {
  const checkDection = (detected) => {
    if (detected) {
      alert('AdBlocker detected');
      return;
    }

    const allEventsPassed = xhrEvents.every((state) => state);
    if (!allEventsPassed) {
      alert('AdBlocker detected');
      return;
    }
    // No AdBlocker detected, do something
    console.log('No AdBlocker detected');
  };

  const url = 'https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js';
  const xhrEvents = [false, false, false, false];
  const xhr = new XMLHttpRequest();

  xhr.onreadystatechange = function () {
    xhrEvents[xhr.readyState - 1] = true;
  
    if (xhr.readyState === 4) {
      checkDection();
    }
  };

  try {
    xhr.open("GET", url, true);
    xhr.send();
  } catch (ex) {
    checkDection(true);
  }
})();

Probably changing this part: https://github.com/AdguardTeam/Scriptlets/blob/804c53527008e62ffcd296e3f36543c472b96fe5/src/scriptlets/prevent-xhr.js#L184-L223

to something like:

Code:
forgedRequest.addEventListener('readystatechange', () => {
  Object.defineProperty(thisArg, 'readyState', { value: forgedRequest.readyState, writable: true }); // it seems that - thisArg.readyState = forgedRequest.readyState - doesn't work, but maybe I didn't check it correctly
  const stateEvent = new Event("readystatechange");
  switch (forgedRequest.readyState) {
    case 1:
      thisArg.dispatchEvent(stateEvent);
      const loadStartEvent = new Event("loadstart");
      thisArg.dispatchEvent(loadStartEvent);
      break;
    case 2:
      thisArg.dispatchEvent(stateEvent);
      const progressEvent = new Event("progress");
      thisArg.dispatchEvent(progressEvent);
      break;
    case 3:
      thisArg.dispatchEvent(stateEvent);
      const loadEvent = new Event("load");
      thisArg.dispatchEvent(loadEvent);
      break;
    case 4:
      const {
        readyState,
        responseURL,
        responseXML,
        statusText
      } = forgedRequest;

      // Mock response object
      Object.defineProperties(thisArg, {
        // original values
        readyState: {
          value: readyState,
          writable: false
        },
        statusText: {
          value: statusText,
          writable: false
        },
        // If the request is blocked, responseURL is an empty string
        responseURL: {
          value: responseURL || thisArg.xhrData.url,
          writable: false
        },
        responseXML: {
          value: responseXML,
          writable: false
        },
        // modified values
        status: {
          value: 200,
          writable: false
        },
        response: {
          value: modifiedResponse,
          writable: false
        },
        responseText: {
          value: modifiedResponseText,
          writable: false
        }
      });

      // Mock events
      setTimeout(() => {
        thisArg.dispatchEvent(stateEvent);
        const loadEndEvent = new Event('loadend');
        thisArg.dispatchEvent(loadEndEvent);
      }, 1);
  }
  hit(source);
});

should fixes it.

AdamWr avatar Apr 02 '24 15:04 AdamWr