Skip to content

Commit 45e62c9

Browse files
committed
Add support for extraMatch in trusted-click-element scriptlet
Related issue: uBlockOrigin/uAssets#20744 (comment) Reference documentation: https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-trusted-scriptlets.md#-%EF%B8%8F-trusted-click-element Except that in uBO's implementation, if a regex is given as value to match, it will be tested against an assembled "key=value" string.
1 parent 728799d commit 45e62c9

File tree

1 file changed

+111
-17
lines changed

1 file changed

+111
-17
lines changed

assets/resources/scriptlets.js

Lines changed: 111 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ function safeSelf() {
7979
if ( `${args[0]}` === '' ) { return; }
8080
this.log('[uBO]', ...args);
8181
},
82+
escapeRegexChars(s) {
83+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
84+
},
8285
initPattern(pattern, options = {}) {
8386
if ( pattern === '' ) {
8487
return { matchAll: true };
@@ -99,8 +102,7 @@ function safeSelf() {
99102
}
100103
if ( options.flags !== undefined ) {
101104
return {
102-
re: new this.RegExp(pattern.replace(
103-
/[.*+?^${}()|[\]\\]/g, '\\$&'),
105+
re: new this.RegExp(this.escapeRegexChars(pattern),
104106
options.flags
105107
),
106108
expect,
@@ -119,7 +121,7 @@ function safeSelf() {
119121
if ( pattern === '' ) { return /^/; }
120122
const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern);
121123
if ( match === null ) {
122-
const reStr = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
124+
const reStr = this.escapeRegexChars(pattern);
123125
return new RegExp(verbatim ? `^${reStr}$` : reStr, flags);
124126
}
125127
try {
@@ -835,9 +837,63 @@ function objectFindOwnerFn(
835837

836838
/******************************************************************************/
837839

840+
builtinScriptlets.push({
841+
name: 'get-all-cookies.fn',
842+
fn: getAllCookiesFn,
843+
});
844+
function getAllCookiesFn() {
845+
return document.cookie.split(/\s*;\s*/).map(s => {
846+
const pos = s.indexOf('=');
847+
if ( pos === 0 ) { return; }
848+
if ( pos === -1 ) { return `${s.trim()}=`; }
849+
const key = s.slice(0, pos).trim();
850+
const value = s.slice(pos+1).trim();
851+
return { key, value };
852+
}).filter(s => s !== undefined);
853+
}
854+
855+
/******************************************************************************/
856+
857+
builtinScriptlets.push({
858+
name: 'get-all-local-storage.fn',
859+
fn: getAllLocalStorageFn,
860+
});
861+
function getAllLocalStorageFn(which = 'localStorage') {
862+
const storage = self[which];
863+
const out = [];
864+
for ( let i = 0; i < storage.length; i++ ) {
865+
const key = storage.key(i);
866+
const value = storage.getItem(key);
867+
return { key, value };
868+
}
869+
return out;
870+
}
871+
872+
/******************************************************************************/
873+
874+
builtinScriptlets.push({
875+
name: 'get-cookie.fn',
876+
fn: getCookieFn,
877+
});
878+
function getCookieFn(
879+
name = ''
880+
) {
881+
for ( const s of document.cookie.split(/\s*;\s*/) ) {
882+
const pos = s.indexOf('=');
883+
if ( pos === -1 ) { continue; }
884+
if ( s.slice(0, pos) !== name ) { continue; }
885+
return s.slice(pos+1).trim();
886+
}
887+
}
888+
889+
/******************************************************************************/
890+
838891
builtinScriptlets.push({
839892
name: 'set-cookie.fn',
840893
fn: setCookieFn,
894+
dependencies: [
895+
'get-cookie.fn',
896+
],
841897
});
842898
function setCookieFn(
843899
trusted = false,
@@ -847,16 +903,7 @@ function setCookieFn(
847903
path = '',
848904
options = {},
849905
) {
850-
const getCookieValue = name => {
851-
for ( const s of document.cookie.split(/\s*;\s*/) ) {
852-
const pos = s.indexOf('=');
853-
if ( pos === -1 ) { continue; }
854-
if ( s.slice(0, pos) !== name ) { continue; }
855-
return s.slice(pos+1);
856-
}
857-
};
858-
859-
const cookieBefore = getCookieValue(name);
906+
const cookieBefore = getCookieFn(name);
860907
if ( cookieBefore !== undefined && options.dontOverwrite ) { return; }
861908
if ( cookieBefore === value && options.reload ) { return; }
862909

@@ -884,7 +931,7 @@ function setCookieFn(
884931
} catch(_) {
885932
}
886933

887-
if ( options.reload && getCookieValue(name) === value ) {
934+
if ( options.reload && getCookieFn(name) === value ) {
888935
window.location.reload();
889936
}
890937
}
@@ -4029,6 +4076,9 @@ function trustedSetSessionStorageItem(key = '', value = '') {
40294076
builtinScriptlets.push({
40304077
name: 'trusted-replace-fetch-response.js',
40314078
requiresTrust: true,
4079+
aliases: [
4080+
'trusted-rpfr.js',
4081+
],
40324082
fn: trustedReplaceFetchResponse,
40334083
dependencies: [
40344084
'replace-fetch-response.fn',
@@ -4140,23 +4190,67 @@ builtinScriptlets.push({
41404190
fn: trustedClickElement,
41414191
world: 'ISOLATED',
41424192
dependencies: [
4193+
'get-all-cookies.fn',
4194+
'get-all-local-storage.fn',
41434195
'run-at-html-element.fn',
41444196
'safe-self.fn',
41454197
],
41464198
});
41474199
function trustedClickElement(
41484200
selectors = '',
4149-
extraMatch = '', // not yet supported
4201+
extraMatch = '',
41504202
delay = ''
41514203
) {
4152-
if ( extraMatch !== '' ) { return; }
4153-
41544204
const safe = safeSelf();
41554205
const extraArgs = safe.getExtraArgs(Array.from(arguments), 3);
41564206
const uboLog = extraArgs.log !== undefined
41574207
? ((...args) => { safe.uboLog(...args); })
41584208
: (( ) => { });
41594209

4210+
if ( extraMatch !== '' ) {
4211+
const assertions = extraMatch.split(',').map(s => {
4212+
const pos1 = s.indexOf(':');
4213+
const s1 = pos1 !== -1 ? s.slice(0, pos1) : s;
4214+
const not = s1.startsWith('!');
4215+
const type = not ? s1.slice(1) : s1;
4216+
const s2 = pos1 !== -1 ? s.slice(pos1+1).trim() : '';
4217+
if ( s2 === '' ) { return; }
4218+
const out = { not, type };
4219+
const match = /^\/(.+)\/(i?)$/.exec(s2);
4220+
if ( match !== null ) {
4221+
out.re = new RegExp(match[1], match[2] || undefined);
4222+
return out;
4223+
}
4224+
const pos2 = s2.indexOf('=');
4225+
const key = pos2 !== -1 ? s2.slice(0, pos2).trim() : s2;
4226+
const value = pos2 !== -1 ? s2.slice(pos2+1).trim() : '';
4227+
out.re = new RegExp(`^${this.escapeRegexChars(key)}=${this.escapeRegexChars(value)}`);
4228+
return out;
4229+
}).filter(details => details !== undefined);
4230+
const allCookies = assertions.some(o => o.type === 'cookie')
4231+
? getAllCookiesFn()
4232+
: [];
4233+
const allStorageItems = assertions.some(o => o.type === 'localStorage')
4234+
? getAllLocalStorageFn()
4235+
: [];
4236+
const hasNeedle = (haystack, needle) => {
4237+
for ( const { key, value } of haystack ) {
4238+
if ( needle.test(`${key}=${value}`) ) { return true; }
4239+
}
4240+
return false;
4241+
};
4242+
for ( const { not, type, re } of assertions ) {
4243+
switch ( type ) {
4244+
case 'cookie':
4245+
if ( hasNeedle(allCookies, re) === not ) { return; }
4246+
break;
4247+
case 'localStorage':
4248+
if ( hasNeedle(allStorageItems, re) === not ) { return; }
4249+
break;
4250+
}
4251+
}
4252+
}
4253+
41604254
const querySelectorEx = (selector, context = document) => {
41614255
const pos = selector.indexOf(' >>> ');
41624256
if ( pos === -1 ) { return context.querySelector(selector); }

0 commit comments

Comments
 (0)