@@ -218,79 +218,100 @@ registerScriptlet(trustedSetAttr, {
218218 *
219219 * @param [behavior]
220220 * Optional. Space-separated tokens which modify the default behavior.
221- * - `asap`: Try to remove the attribute as soon as possible. Default behavior
222- * is to remove the attribute(s) asynchronously.
223221 * - `stay`: Keep trying to remove the specified attribute(s) on DOM mutations.
222+ *
223+ * @params [...varargs]
224+ * Optional, any of following pairs of parameters:
225+ * - `quitAfter, sec`: where `sec` is the number of seconds after which the
226+ * scriptlet ceases to be active. This has precedence over `stay` behavior.
224227 * */
225228
226229export function removeAttr (
227230 rawToken = '' ,
228231 rawSelector = '' ,
229- behavior = ''
232+ behavior = '' ,
233+ ...varargs
230234) {
231235 if ( typeof rawToken !== 'string' ) { return ; }
232236 if ( rawToken === '' ) { return ; }
233237 const safe = safeSelf ( ) ;
234- const logPrefix = safe . makeLogPrefix ( 'remove-attr' , rawToken , rawSelector , behavior ) ;
238+ const logPrefix = safe . makeLogPrefix ( 'remove-attr' ,
239+ rawToken , rawSelector , behavior , ...varargs
240+ ) ;
235241 const tokens = safe . String_split . call ( rawToken , / \s * \| \s * / ) ;
236- const selector = tokens
237- . map ( a => `${ rawSelector } [${ CSS . escape ( a ) } ]` )
238- . join ( ',' ) ;
242+ const selector = tokens . map ( a => {
243+ const b = CSS . escape ( a ) ;
244+ return rawSelector . includes ( `[${ b } ]` ) ? rawSelector : `${ rawSelector } [${ b } ]` ;
245+ } ) . join ( ',' ) ;
246+ const lazily = / \b a s a p \b / . test ( behavior ) === false ;
247+ const options = safe . parseVarargs ( varargs ) ;
239248 if ( safe . logLevel > 1 ) {
240249 safe . uboLog ( logPrefix , `Target selector:\n\t${ selector } ` ) ;
241250 }
242- const asap = / \b a s a p \b / . test ( behavior ) ;
243- let timerId ;
244- const rmattrAsync = ( ) => {
245- if ( timerId !== undefined ) { return ; }
246- timerId = onIdleFn ( ( ) => {
247- timerId = undefined ;
248- rmattr ( ) ;
249- } , { timeout : 17 } ) ;
250- } ;
251- const rmattr = ( ) => {
252- if ( timerId !== undefined ) {
253- offIdleFn ( timerId ) ;
254- timerId = undefined ;
251+ const rmattrFromNode = node => {
252+ for ( const attr of tokens ) {
253+ if ( node . hasAttribute ( attr ) === false ) { continue ; }
254+ node . removeAttribute ( attr ) ;
255+ safe . uboLog ( logPrefix , `Removed attribute '${ attr } '` ) ;
255256 }
256- try {
257- const nodes = document . querySelectorAll ( selector ) ;
258- for ( const node of nodes ) {
259- for ( const attr of tokens ) {
260- if ( node . hasAttribute ( attr ) === false ) { continue ; }
261- node . removeAttribute ( attr ) ;
262- safe . uboLog ( logPrefix , `Removed attribute '${ attr } '` ) ;
263- }
264- }
265- } catch {
257+ } ;
258+ const rmattr = nodes => {
259+ for ( const node of nodes ?? document . querySelectorAll ( selector ) ) {
260+ rmattrFromNode ( node ) ;
266261 }
267262 } ;
263+ const rmAttrLazily = ( ) => {
264+ if ( rmAttrLazily . timer !== undefined ) { return ; }
265+ rmAttrLazily . timer = onIdleFn ( ( ) => {
266+ rmAttrLazily . timer = undefined ;
267+ rmattr ( ) ;
268+ } , { timeout : 17 } ) ;
269+ } ;
268270 const mutationHandler = mutations => {
269- if ( timerId !== undefined ) { return ; }
270- let skip = true ;
271- for ( let i = 0 ; i < mutations . length && skip ; i ++ ) {
272- const { type, addedNodes, removedNodes } = mutations [ i ] ;
273- if ( type === 'attributes' ) { skip = false ; }
274- for ( let j = 0 ; j < addedNodes . length && skip ; j ++ ) {
275- if ( addedNodes [ j ] . nodeType === 1 ) { skip = false ; break ; }
271+ for ( const { addedNodes, removedNodes } of mutations ) {
272+ for ( const node of addedNodes ) {
273+ if ( node . nodeType !== 1 ) { continue ; }
274+ if ( lazily ) { return rmAttrLazily ( ) ; }
275+ if ( node . matches ( selector ) ) {
276+ rmattrFromNode ( node ) ;
277+ }
278+ if ( node . childElementCount ) {
279+ rmattr ( node . querySelectorAll ( selector ) ) ;
280+ }
276281 }
277- for ( let j = 0 ; j < removedNodes . length && skip ; j ++ ) {
278- if ( removedNodes [ j ] . nodeType === 1 ) { skip = false ; break ; }
282+ if ( lazily ) { return ; }
283+ for ( const node of removedNodes ) {
284+ if ( node . nodeType !== 1 ) { continue ; }
285+ if ( node . matches ( selector ) ) {
286+ rmattrFromNode ( node ) ;
287+ }
279288 }
280289 }
281- if ( skip ) { return ; }
282- asap ? rmattr ( ) : rmattrAsync ( ) ;
283290 } ;
284291 const start = ( ) => {
285292 rmattr ( ) ;
286- if ( / \b s t a y \b / . test ( behavior ) === false ) { return ; }
293+ if ( / \b s t a y \b / . test ( behavior ) === false ) {
294+ if ( options . quitAfter === undefined ) { return ; }
295+ }
287296 const observer = new MutationObserver ( mutationHandler ) ;
288297 observer . observe ( document , {
289298 attributes : true ,
290299 attributeFilter : tokens ,
291300 childList : true ,
292301 subtree : true ,
293302 } ) ;
303+ if ( options . quitAfter ) {
304+ self . setTimeout ( ( ) => {
305+ observer . disconnect ( ) ;
306+ if ( rmAttrLazily . timer ) {
307+ offIdleFn ( rmAttrLazily . timer ) ;
308+ rmAttrLazily . timer = undefined ;
309+ }
310+ if ( safe . logLevel > 1 ) {
311+ safe . uboLog ( logPrefix , 'Quitting' ) ;
312+ }
313+ } , options . quitAfter * 1000 ) ;
314+ }
294315 } ;
295316 runAt ( ( ) => { start ( ) ; } , safe . String_split . call ( behavior , / \s + / ) ) ;
296317}
0 commit comments