@@ -198,4 +198,108 @@ trustedSetAttr.details = {
198198 world : 'ISOLATED' ,
199199} ;
200200
201+ /**
202+ * @scriptlet remove-attr
203+ *
204+ * @description
205+ * Remove one or more attributes from a set of elements.
206+ *
207+ * @param attribute
208+ * The name of the attribute(s) to remove. This can be a list of space-
209+ * separated attribute names.
210+ *
211+ * @param [selector]
212+ * Optional. A CSS selector for the elements to target. Default to
213+ * `[attribute]`, or `[attribute1],[attribute2],...` if more than one
214+ * attribute name is specified.
215+ *
216+ * @param [behavior]
217+ * Optional. Space-separated tokens which modify the default behavior.
218+ * - `asap`: Try to remove the attribute as soon as possible. Default behavior
219+ * is to remove the attribute(s) asynchronously.
220+ * - `stay`: Keep trying to remove the specified attribute(s) on DOM mutations.
221+ * */
222+
223+ export function removeAttr (
224+ rawToken = '' ,
225+ rawSelector = '' ,
226+ behavior = ''
227+ ) {
228+ if ( typeof rawToken !== 'string' ) { return ; }
229+ if ( rawToken === '' ) { return ; }
230+ const safe = safeSelf ( ) ;
231+ const logPrefix = safe . makeLogPrefix ( 'remove-attr' , rawToken , rawSelector , behavior ) ;
232+ const tokens = rawToken . split ( / \s * \| \s * / ) ;
233+ const selector = tokens
234+ . map ( a => `${ rawSelector } [${ CSS . escape ( a ) } ]` )
235+ . join ( ',' ) ;
236+ if ( safe . logLevel > 1 ) {
237+ safe . uboLog ( logPrefix , `Target selector:\n\t${ selector } ` ) ;
238+ }
239+ const asap = / \b a s a p \b / . test ( behavior ) ;
240+ let timerId ;
241+ const rmattrAsync = ( ) => {
242+ if ( timerId !== undefined ) { return ; }
243+ timerId = safe . onIdle ( ( ) => {
244+ timerId = undefined ;
245+ rmattr ( ) ;
246+ } , { timeout : 17 } ) ;
247+ } ;
248+ const rmattr = ( ) => {
249+ if ( timerId !== undefined ) {
250+ safe . offIdle ( timerId ) ;
251+ timerId = undefined ;
252+ }
253+ try {
254+ const nodes = document . querySelectorAll ( selector ) ;
255+ for ( const node of nodes ) {
256+ for ( const attr of tokens ) {
257+ if ( node . hasAttribute ( attr ) === false ) { continue ; }
258+ node . removeAttribute ( attr ) ;
259+ safe . uboLog ( logPrefix , `Removed attribute '${ attr } '` ) ;
260+ }
261+ }
262+ } catch ( ex ) {
263+ }
264+ } ;
265+ const mutationHandler = mutations => {
266+ if ( timerId !== undefined ) { return ; }
267+ let skip = true ;
268+ for ( let i = 0 ; i < mutations . length && skip ; i ++ ) {
269+ const { type, addedNodes, removedNodes } = mutations [ i ] ;
270+ if ( type === 'attributes' ) { skip = false ; }
271+ for ( let j = 0 ; j < addedNodes . length && skip ; j ++ ) {
272+ if ( addedNodes [ j ] . nodeType === 1 ) { skip = false ; break ; }
273+ }
274+ for ( let j = 0 ; j < removedNodes . length && skip ; j ++ ) {
275+ if ( removedNodes [ j ] . nodeType === 1 ) { skip = false ; break ; }
276+ }
277+ }
278+ if ( skip ) { return ; }
279+ asap ? rmattr ( ) : rmattrAsync ( ) ;
280+ } ;
281+ const start = ( ) => {
282+ rmattr ( ) ;
283+ if ( / \b s t a y \b / . test ( behavior ) === false ) { return ; }
284+ const observer = new MutationObserver ( mutationHandler ) ;
285+ observer . observe ( document , {
286+ attributes : true ,
287+ attributeFilter : tokens ,
288+ childList : true ,
289+ subtree : true ,
290+ } ) ;
291+ } ;
292+ runAt ( ( ) => { start ( ) ; } , behavior . split ( / \s + / ) ) ;
293+ }
294+ removeAttr . details = {
295+ name : 'remove-attr.js' ,
296+ aliases : [
297+ 'ra.js' ,
298+ ] ,
299+ dependencies : [
300+ runAt ,
301+ safeSelf ,
302+ ] ,
303+ } ;
304+
201305/******************************************************************************/
0 commit comments