Skip to content

Commit b7b53ee

Browse files
committed
[mv3] Add support for no-xhr-if/no-fetch-if scriptlets
1 parent 8c33847 commit b7b53ee

File tree

5 files changed

+298
-2
lines changed

5 files changed

+298
-2
lines changed

platform/mv3/make-rulesets.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -942,7 +942,10 @@ async function processDomainScriptletFilters(assetDetails, domainBased, original
942942
continue;
943943
}
944944
const normalized = parseScriptletFilter(rawFilter, originalScriptletMap);
945-
if ( normalized === undefined ) { continue; }
945+
if ( normalized === undefined ) {
946+
log(`Discarded unsupported scriptlet filter: ${rawFilter}`, true);
947+
continue;
948+
}
946949
let argsDetails = scriptletDetails.get(normalized.token);
947950
if ( argsDetails === undefined ) {
948951
argsDetails = new Map();
@@ -1046,7 +1049,10 @@ async function processEntityScriptletFilters(assetDetails, entityBased, original
10461049
continue;
10471050
}
10481051
const normalized = parseScriptletFilter(rawFilter, originalScriptletMap, '.entity');
1049-
if ( normalized === undefined ) { continue; }
1052+
if ( normalized === undefined ) {
1053+
log(`Discarded unsupported scriptlet filter: ${rawFilter}`, true);
1054+
continue;
1055+
}
10501056
let argsDetails = scriptletMap.get(normalized.token);
10511057
if ( argsDetails === undefined ) {
10521058
argsDetails = new Map();

platform/mv3/scriptlets/no-addeventlistener-if.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
/// name no-addeventlistener-if
3232
/// alias noaelif
33+
/// alias addEventListener-defuser
3334
/// alias aeld
3435

3536
/******************************************************************************/
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*******************************************************************************
2+
3+
uBlock Origin - a browser extension to block requests.
4+
Copyright (C) 2019-present Raymond Hill
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see {http://www.gnu.org/licenses/}.
18+
19+
Home: https://github.com/gorhill/uBlock
20+
21+
The scriptlets below are meant to be injected only into a
22+
web page context.
23+
*/
24+
25+
/* jshint esversion:11 */
26+
27+
'use strict';
28+
29+
/******************************************************************************/
30+
31+
/// name no-fetch-if
32+
33+
/******************************************************************************/
34+
35+
// Important!
36+
// Isolate from global scope
37+
(function uBOL_noFetchIf() {
38+
39+
/******************************************************************************/
40+
41+
// $rulesetId$
42+
43+
const argsList = self.$argsList$;
44+
45+
const hostnamesMap = new Map(self.$hostnamesMap$);
46+
47+
/******************************************************************************/
48+
49+
const scriptlet = (
50+
conditions = ''
51+
) => {
52+
const needles = [];
53+
for ( const condition of conditions.split(/\s+/) ) {
54+
if ( condition === '' ) { continue; }
55+
const pos = condition.indexOf(':');
56+
let key, value;
57+
if ( pos !== -1 ) {
58+
key = condition.slice(0, pos);
59+
value = condition.slice(pos + 1);
60+
} else {
61+
key = 'url';
62+
value = condition;
63+
}
64+
if ( value === '' ) {
65+
value = '^';
66+
} else if ( value.startsWith('/') && value.endsWith('/') ) {
67+
value = value.slice(1, -1);
68+
} else {
69+
value = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
70+
}
71+
needles.push({ key, re: new RegExp(value) });
72+
}
73+
self.fetch = new Proxy(self.fetch, {
74+
apply: function(target, thisArg, args) {
75+
let proceed = true;
76+
try {
77+
let details;
78+
if ( args[0] instanceof self.Request ) {
79+
details = args[0];
80+
} else {
81+
details = Object.assign({ url: args[0] }, args[1]);
82+
}
83+
const props = new Map();
84+
for ( const prop in details ) {
85+
let v = details[prop];
86+
if ( typeof v !== 'string' ) {
87+
try { v = JSON.stringify(v); }
88+
catch(ex) { }
89+
}
90+
if ( typeof v !== 'string' ) { continue; }
91+
props.set(prop, v);
92+
}
93+
proceed = needles.length === 0;
94+
for ( const { key, re } of needles ) {
95+
if (
96+
props.has(key) === false ||
97+
re.test(props.get(key)) === false
98+
) {
99+
proceed = true;
100+
break;
101+
}
102+
}
103+
} catch(ex) {
104+
}
105+
return proceed
106+
? Reflect.apply(target, thisArg, args)
107+
: Promise.resolve(new Response());
108+
}
109+
});
110+
};
111+
112+
/******************************************************************************/
113+
114+
let hn;
115+
try { hn = document.location.hostname; } catch(ex) { }
116+
while ( hn ) {
117+
if ( hostnamesMap.has(hn) ) {
118+
let argsIndices = hostnamesMap.get(hn);
119+
if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
120+
for ( const argsIndex of argsIndices ) {
121+
const details = argsList[argsIndex];
122+
if ( details.n && details.n.includes(hn) ) { continue; }
123+
try { scriptlet(...details.a); } catch(ex) {}
124+
}
125+
}
126+
if ( hn === '*' ) { break; }
127+
const pos = hn.indexOf('.');
128+
if ( pos !== -1 ) {
129+
hn = hn.slice(pos + 1);
130+
} else {
131+
hn = '*';
132+
}
133+
}
134+
135+
argsList.length = 0;
136+
hostnamesMap.clear();
137+
138+
/******************************************************************************/
139+
140+
})();
141+
142+
/******************************************************************************/
143+

platform/mv3/scriptlets/no-windowopen-if.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
/// name no-windowopen-if
3232
/// alias no-windowOpen-if
3333
/// alias nowoif
34+
/// alias window.open-defuser
3435

3536
/******************************************************************************/
3637

platform/mv3/scriptlets/no-xhr-if.js

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*******************************************************************************
2+
3+
uBlock Origin - a browser extension to block requests.
4+
Copyright (C) 2019-present Raymond Hill
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see {http://www.gnu.org/licenses/}.
18+
19+
Home: https://github.com/gorhill/uBlock
20+
21+
The scriptlets below are meant to be injected only into a
22+
web page context.
23+
*/
24+
25+
/* jshint esversion:11 */
26+
27+
'use strict';
28+
29+
/******************************************************************************/
30+
31+
/// name no-xhr-if
32+
33+
/******************************************************************************/
34+
35+
// Important!
36+
// Isolate from global scope
37+
(function uBOL_noXhrIf() {
38+
39+
/******************************************************************************/
40+
41+
// $rulesetId$
42+
43+
const argsList = self.$argsList$;
44+
45+
const hostnamesMap = new Map(self.$hostnamesMap$);
46+
47+
/******************************************************************************/
48+
49+
const scriptlet = (
50+
conditions = ''
51+
) => {
52+
const xhrInstances = new WeakMap();
53+
const needles = [];
54+
for ( const condition of conditions.split(/\s+/) ) {
55+
if ( condition === '' ) { continue; }
56+
const pos = condition.indexOf(':');
57+
let key, value;
58+
if ( pos !== -1 ) {
59+
key = condition.slice(0, pos);
60+
value = condition.slice(pos + 1);
61+
} else {
62+
key = 'url';
63+
value = condition;
64+
}
65+
if ( value === '' ) {
66+
value = '^';
67+
} else if ( value.startsWith('/') && value.endsWith('/') ) {
68+
value = value.slice(1, -1);
69+
} else {
70+
value = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
71+
}
72+
needles.push({ key, re: new RegExp(value) });
73+
}
74+
self.XMLHttpRequest = class extends self.XMLHttpRequest {
75+
open(...args) {
76+
const argNames = [ 'method', 'url' ];
77+
const haystack = new Map();
78+
for ( let i = 0; i < args.length && i < argNames.length; i++ ) {
79+
haystack.set(argNames[i], args[i]);
80+
}
81+
if ( haystack.size !== 0 ) {
82+
let matches = true;
83+
for ( const { key, re } of needles ) {
84+
matches = re.test(haystack.get(key) || '');
85+
if ( matches === false ) { break; }
86+
}
87+
if ( matches ) {
88+
xhrInstances.set(this, haystack);
89+
}
90+
}
91+
return super.open(...args);
92+
}
93+
send(...args) {
94+
const haystack = xhrInstances.get(this);
95+
if ( haystack === undefined ) {
96+
return super.send(...args);
97+
}
98+
Object.defineProperties(this, {
99+
readyState: { value: 4, writable: false },
100+
response: { value: '', writable: false },
101+
responseText: { value: '', writable: false },
102+
responseURL: { value: haystack.get('url'), writable: false },
103+
responseXML: { value: '', writable: false },
104+
status: { value: 200, writable: false },
105+
statusText: { value: 'OK', writable: false },
106+
});
107+
this.dispatchEvent(new Event('readystatechange'));
108+
this.dispatchEvent(new Event('load'));
109+
this.dispatchEvent(new Event('loadend'));
110+
}
111+
};
112+
};
113+
114+
/******************************************************************************/
115+
116+
let hn;
117+
try { hn = document.location.hostname; } catch(ex) { }
118+
while ( hn ) {
119+
if ( hostnamesMap.has(hn) ) {
120+
let argsIndices = hostnamesMap.get(hn);
121+
if ( typeof argsIndices === 'number' ) { argsIndices = [ argsIndices ]; }
122+
for ( const argsIndex of argsIndices ) {
123+
const details = argsList[argsIndex];
124+
if ( details.n && details.n.includes(hn) ) { continue; }
125+
try { scriptlet(...details.a); } catch(ex) {}
126+
}
127+
}
128+
if ( hn === '*' ) { break; }
129+
const pos = hn.indexOf('.');
130+
if ( pos !== -1 ) {
131+
hn = hn.slice(pos + 1);
132+
} else {
133+
hn = '*';
134+
}
135+
}
136+
137+
argsList.length = 0;
138+
hostnamesMap.clear();
139+
140+
/******************************************************************************/
141+
142+
})();
143+
144+
/******************************************************************************/
145+

0 commit comments

Comments
 (0)