@@ -2557,11 +2557,46 @@ function m3uPrune(
2557
2557
} ) ;
2558
2558
}
2559
2559
2560
- /******************************************************************************/
2560
+ /*******************************************************************************
2561
+ *
2562
+ * @scriptlet href-sanitizer
2563
+ *
2564
+ * @description
2565
+ * Set the `href` attribute to a value found in the DOM at, or below the
2566
+ * targeted `a` element.
2567
+ *
2568
+ * ### Syntax
2569
+ *
2570
+ * ```text
2571
+ * example.org##+js(href-sanitizer, selector [, source])
2572
+ * ```
2573
+ *
2574
+ * - `selector`: required, CSS selector, specifies `a` elements for which the
2575
+ * `href` attribute must be overriden.
2576
+ * - `source`: optional, default to `text`, specifies from where to get the
2577
+ * value which will override the `href` attribute.
2578
+ * - `text`: the value will be the first valid URL found in the text
2579
+ * content of the targeted `a` element.
2580
+ * - `[attr]`: the value will be the attribute _attr_ of the targeted `a`
2581
+ * element.
2582
+ * - `?param`: the value will be the query parameter _param_ of the URL
2583
+ * found in the `href` attribute of the targeted `a` element.
2584
+ *
2585
+ * ### Examples
2586
+ *
2587
+ * example.org##+js(href-sanitizer, a)
2588
+ * example.org##+js(href-sanitizer, a[title], [title])
2589
+ * example.org##+js(href-sanitizer, a[href*="/away.php?to="], ?to)
2590
+ *
2591
+ * */
2561
2592
2562
2593
builtinScriptlets . push ( {
2563
2594
name : 'href-sanitizer.js' ,
2564
2595
fn : hrefSanitizer ,
2596
+ world : 'ISOLATED' ,
2597
+ dependencies : [
2598
+ 'run-at.fn' ,
2599
+ ] ,
2565
2600
} ) ;
2566
2601
function hrefSanitizer (
2567
2602
selector = '' ,
@@ -2659,14 +2694,32 @@ function hrefSanitizer(
2659
2694
childList : true ,
2660
2695
} ) ;
2661
2696
} ;
2662
- if ( document . readyState === 'loading' ) {
2663
- document . addEventListener ( 'DOMContentLoaded' , start , { once : true } ) ;
2664
- } else {
2665
- start ( ) ;
2666
- }
2697
+ runAt ( ( ) => { start ( ) ; } , 'interactive' ) ;
2667
2698
}
2668
2699
2669
- /******************************************************************************/
2700
+ /*******************************************************************************
2701
+ *
2702
+ * @scriptlet call-nothrow
2703
+ *
2704
+ * @description
2705
+ * Prevent a function call from throwing. The function will be called, however
2706
+ * should it throw, the scriptlet will silently process the exception and
2707
+ * returns as if no exception has occurred.
2708
+ *
2709
+ * ### Syntax
2710
+ *
2711
+ * ```text
2712
+ * example.org##+js(call-nothrow, propertyChain)
2713
+ * ```
2714
+ *
2715
+ * - `propertyChain`: a chain of dot-separated properties which leads to the
2716
+ * function to be trapped.
2717
+ *
2718
+ * ### Examples
2719
+ *
2720
+ * example.org##+js(call-nothrow, Object.defineProperty)
2721
+ *
2722
+ * */
2670
2723
2671
2724
builtinScriptlets . push ( {
2672
2725
name : 'call-nothrow.js' ,
@@ -2899,6 +2952,118 @@ function setSessionStorageItem(key = '', value = '') {
2899
2952
setLocalStorageItemCore ( 'session' , false , key , value ) ;
2900
2953
}
2901
2954
2955
+ /*******************************************************************************
2956
+ *
2957
+ * @scriptlet set-attr
2958
+ *
2959
+ * @description
2960
+ * Sets the specified attribute on the specified elements. This scriptlet runs
2961
+ * once when the page loads then afterward on DOM mutations.
2962
+
2963
+ * Reference: https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-attr.js
2964
+ *
2965
+ * ### Syntax
2966
+ *
2967
+ * ```text
2968
+ * example.org##+js(set-attr, selector, attr [, value])
2969
+ * ```
2970
+ *
2971
+ * - `selector`: CSS selector of DOM elements for which the attribute `attr`
2972
+ * must be modified.
2973
+ * - `attr`: the name of the attribute to modify
2974
+ * - `value`: the value to assign to the target attribute. Possible values:
2975
+ * - `''`: empty string (default)
2976
+ * - `true`
2977
+ * - `false`
2978
+ * - positive decimal integer 0 <= value < 32768
2979
+ * - `[other]`: copy the value from attribute `other` on the same element
2980
+ * */
2981
+
2982
+ builtinScriptlets . push ( {
2983
+ name : 'set-attr.js' ,
2984
+ fn : setAttr ,
2985
+ world : 'ISOLATED' ,
2986
+ dependencies : [
2987
+ 'run-at.fn' ,
2988
+ ] ,
2989
+ } ) ;
2990
+ function setAttr (
2991
+ selector = '' ,
2992
+ attr = '' ,
2993
+ value = ''
2994
+ ) {
2995
+ if ( typeof selector !== 'string' ) { return ; }
2996
+ if ( selector === '' ) { return ; }
2997
+ if ( value === '' ) { return ; }
2998
+
2999
+ const validValues = [ '' , 'false' , 'true' ] ;
3000
+ let copyFrom = '' ;
3001
+
3002
+ if ( validValues . includes ( value ) === false ) {
3003
+ if ( / ^ \d + $ / . test ( value ) ) {
3004
+ const n = parseInt ( value , 10 ) ;
3005
+ if ( n >= 32768 ) { return ; }
3006
+ value = `${ n } ` ;
3007
+ } else if ( / ^ \[ .+ \] $ / . test ( value ) ) {
3008
+ copyFrom = value . slice ( 1 , - 1 ) ;
3009
+ } else {
3010
+ return ;
3011
+ }
3012
+ }
3013
+
3014
+ const extractValue = elem => {
3015
+ if ( copyFrom !== '' ) {
3016
+ return elem . getAttribute ( copyFrom ) || '' ;
3017
+ }
3018
+ return value ;
3019
+ } ;
3020
+
3021
+ const applySetAttr = ( ) => {
3022
+ const elems = [ ] ;
3023
+ try {
3024
+ elems . push ( ...document . querySelectorAll ( selector ) ) ;
3025
+ }
3026
+ catch ( ex ) {
3027
+ return false ;
3028
+ }
3029
+ for ( const elem of elems ) {
3030
+ const before = elem . getAttribute ( attr ) ;
3031
+ const after = extractValue ( elem ) ;
3032
+ if ( after === before ) { continue ; }
3033
+ elem . setAttribute ( attr , after ) ;
3034
+ }
3035
+ return true ;
3036
+ } ;
3037
+ let observer , timer ;
3038
+ const onDomChanged = mutations => {
3039
+ if ( timer !== undefined ) { return ; }
3040
+ let shouldWork = false ;
3041
+ for ( const mutation of mutations ) {
3042
+ if ( mutation . addedNodes . length === 0 ) { continue ; }
3043
+ for ( const node of mutation . addedNodes ) {
3044
+ if ( node . nodeType !== 1 ) { continue ; }
3045
+ shouldWork = true ;
3046
+ break ;
3047
+ }
3048
+ if ( shouldWork ) { break ; }
3049
+ }
3050
+ if ( shouldWork === false ) { return ; }
3051
+ timer = self . requestAnimationFrame ( ( ) => {
3052
+ timer = undefined ;
3053
+ applySetAttr ( ) ;
3054
+ } ) ;
3055
+ } ;
3056
+ const start = ( ) => {
3057
+ if ( applySetAttr ( ) === false ) { return ; }
3058
+ observer = new MutationObserver ( onDomChanged ) ;
3059
+ observer . observe ( document . body , {
3060
+ subtree : true ,
3061
+ childList : true ,
3062
+ } ) ;
3063
+ } ;
3064
+ runAt ( ( ) => { start ( ) ; } , 'idle' ) ;
3065
+ }
3066
+
2902
3067
2903
3068
/*******************************************************************************
2904
3069
*
0 commit comments