AdguardTeam / Scriptlets

AdGuard scriptlets library
GNU General Public License v3.0
138 stars 27 forks source link

Improve prevent-xhr — add missed events #414

Open AdamWr opened 3 months ago

AdamWr commented 3 months ago

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')
  2. Go to - https://example.org/
  3. 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: ```js 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.