Improve 'prevent-xhr' — set responseURL on state 2 and skip first state if onreadystatechange was declared after open call
It seems that there are 2 issues in prevent-xhr.
First one is that we set responseURL on state 4 but it looks like that it should be done on state 2.
https://github.com/AdguardTeam/Scriptlets/blob/3ba245f96237915bbff6fe80c7400b03c0f5014d/src/scriptlets/prevent-xhr.js#L191-L201
Example detection code:
(() => {
const checkDetection = (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 >= 2 && xhr.responseURL !== url) {
checkDetection(true);
}
if (xhr.readyState === 4) {
checkDetection();
}
};
try {
xhr.open("GET", url, true);
xhr.send();
} catch (ex) {
console.error(ex);
checkDetection(true);
}
})();
Second issue is that onreadystatechange depends on the order how xhr has been called.
If onreadystatechange is declared before open call then there will be 4 states (1-4), but if it's declared after open call then there should be 3 states (2-4).
Example detection code:
(() => {
let detectionState = false;
const checkDetection = (detected) => {
if (detectionState) {
return;
}
if (detected) {
alert('AdBlocker detected');
detectionState = true;
return;
}
const allEventsPassed = xhrEvents.every(state => state);
if (!allEventsPassed) {
alert('AdBlocker detected');
detectionState = true;
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];
const xhr = new XMLHttpRequest();
try {
xhr.open("GET", url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === 1) {
checkDetection(true);
return;
}
xhrEvents[xhr.readyState - 2] = true;
if (xhr.readyState >= 2 && xhr.responseURL !== url) {
checkDetection(true);
return;
}
if (xhr.readyState === 4) {
checkDetection();
}
};
xhr.send();
} catch (ex) {
console.error(ex);
checkDetection(true);
}
})();
Maybe we could just check in xhr.open if xhr.onreadystatechange is a function, if so, it was declared before xhr.open and 1-4 states should be invoked. But if not, then first state should be skipped.
Or perhaps there is a better solution.
Steps to reproduce:
- Add to user rules:
example.org#%#//scriptlet('prevent-xhr', 'pagead2.googlesyndication.com')
- Go to - https://example.org/
- Run mentioned scripts in browser console