Skip to content

Commit bd98bca

Browse files
committed
Improve remove-attr scriptlet
1 parent 87d21ff commit bd98bca

1 file changed

Lines changed: 63 additions & 42 deletions

File tree

src/js/resources/attribute.js

Lines changed: 63 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -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

226229
export 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 = /\basap\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 = /\basap\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 ( /\bstay\b/.test(behavior) === false ) { return; }
293+
if ( /\bstay\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

Comments
 (0)