@@ -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 = / ^ \/ ( .+ ) \/ ( [ g i m s u ] * ) $ / . 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+
838891builtinScriptlets . push ( {
839892 name : 'set-cookie.fn' ,
840893 fn : setCookieFn ,
894+ dependencies : [
895+ 'get-cookie.fn' ,
896+ ] ,
841897} ) ;
842898function 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 = '') {
40294076builtinScriptlets . 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} ) ;
41474199function 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