summaryrefslogtreecommitdiff
path: root/data/extensions/uBlock0@raymondhill.net/js/resources/attribute.js
diff options
context:
space:
mode:
Diffstat (limited to 'data/extensions/uBlock0@raymondhill.net/js/resources/attribute.js')
-rw-r--r--data/extensions/uBlock0@raymondhill.net/js/resources/attribute.js305
1 files changed, 305 insertions, 0 deletions
diff --git a/data/extensions/uBlock0@raymondhill.net/js/resources/attribute.js b/data/extensions/uBlock0@raymondhill.net/js/resources/attribute.js
new file mode 100644
index 0000000..3b33a50
--- /dev/null
+++ b/data/extensions/uBlock0@raymondhill.net/js/resources/attribute.js
@@ -0,0 +1,305 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2019-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+
+*/
+
+import { registerScriptlet } from './base.js';
+import { runAt } from './run-at.js';
+import { safeSelf } from './safe-self.js';
+
+/******************************************************************************/
+
+export function setAttrFn(
+ trusted = false,
+ logPrefix,
+ selector = '',
+ attr = '',
+ value = ''
+) {
+ if ( selector === '' ) { return; }
+ if ( attr === '' ) { return; }
+
+ const safe = safeSelf();
+ const copyFrom = trusted === false && /^\[.+\]$/.test(value)
+ ? value.slice(1, -1)
+ : '';
+
+ const extractValue = elem => copyFrom !== ''
+ ? elem.getAttribute(copyFrom) || ''
+ : value;
+
+ const applySetAttr = ( ) => {
+ let elems;
+ try {
+ elems = document.querySelectorAll(selector);
+ } catch {
+ return false;
+ }
+ for ( const elem of elems ) {
+ const before = elem.getAttribute(attr);
+ const after = extractValue(elem);
+ if ( after === before ) { continue; }
+ if ( after !== '' && /^on/i.test(attr) ) {
+ if ( attr.toLowerCase() in elem ) { continue; }
+ }
+ elem.setAttribute(attr, after);
+ safe.uboLog(logPrefix, `${attr}="${after}"`);
+ }
+ return true;
+ };
+
+ let observer, timer;
+ const onDomChanged = mutations => {
+ if ( timer !== undefined ) { return; }
+ let shouldWork = false;
+ for ( const mutation of mutations ) {
+ if ( mutation.addedNodes.length === 0 ) { continue; }
+ for ( const node of mutation.addedNodes ) {
+ if ( node.nodeType !== 1 ) { continue; }
+ shouldWork = true;
+ break;
+ }
+ if ( shouldWork ) { break; }
+ }
+ if ( shouldWork === false ) { return; }
+ timer = self.requestAnimationFrame(( ) => {
+ timer = undefined;
+ applySetAttr();
+ });
+ };
+
+ const start = ( ) => {
+ if ( applySetAttr() === false ) { return; }
+ observer = new MutationObserver(onDomChanged);
+ observer.observe(document.body, {
+ subtree: true,
+ childList: true,
+ });
+ };
+ runAt(( ) => { start(); }, 'idle');
+}
+registerScriptlet(setAttrFn, {
+ name: 'set-attr.fn',
+ dependencies: [
+ runAt,
+ safeSelf,
+ ],
+});
+
+/**
+ * @scriptlet set-attr
+ *
+ * @description
+ * Sets the specified attribute on the specified elements. This scriptlet runs
+ * once when the page loads then afterward on DOM mutations.
+ *
+ * Reference: https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-attr.js
+ *
+ * @param selector
+ * A CSS selector for the elements to target.
+ *
+ * @param attr
+ * The name of the attribute to modify.
+ *
+ * @param value
+ * The new value of the attribute. Supported values:
+ * - `''`: empty string (default)
+ * - `true`
+ * - `false`
+ * - positive decimal integer 0 <= value < 32768
+ * - `[other]`: copy the value from attribute `other` on the same element
+ *
+ * */
+
+export function setAttr(
+ selector = '',
+ attr = '',
+ value = ''
+) {
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('set-attr', selector, attr, value);
+ const validValues = [ '', 'false', 'true' ];
+
+ if ( validValues.includes(value.toLowerCase()) === false ) {
+ if ( /^\d+$/.test(value) ) {
+ const n = parseInt(value, 10);
+ if ( n >= 32768 ) { return; }
+ value = `${n}`;
+ } else if ( /^\[.+\]$/.test(value) === false ) {
+ return;
+ }
+ }
+
+ setAttrFn(false, logPrefix, selector, attr, value);
+}
+registerScriptlet(setAttr, {
+ name: 'set-attr.js',
+ dependencies: [
+ safeSelf,
+ setAttrFn,
+ ],
+ world: 'ISOLATED',
+});
+
+/**
+ * @trustedScriptlet trusted-set-attr
+ *
+ * @description
+ * Sets the specified attribute on the specified elements. This scriptlet runs
+ * once when the page loads then afterward on DOM mutations.
+ *
+ * Reference: https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-trusted-scriptlets.md#-%EF%B8%8F-trusted-set-attr
+ *
+ * @param selector
+ * A CSS selector for the elements to target.
+ *
+ * @param attr
+ * The name of the attribute to modify.
+ *
+ * @param value
+ * The new value of the attribute. Since the scriptlet requires a trusted
+ * source, the value can be anything.
+ *
+ * */
+
+export function trustedSetAttr(
+ selector = '',
+ attr = '',
+ value = ''
+) {
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('trusted-set-attr', selector, attr, value);
+ setAttrFn(true, logPrefix, selector, attr, value);
+}
+registerScriptlet(trustedSetAttr, {
+ name: 'trusted-set-attr.js',
+ requiresTrust: true,
+ dependencies: [
+ safeSelf,
+ setAttrFn,
+ ],
+ world: 'ISOLATED',
+});
+
+/**
+ * @scriptlet remove-attr
+ *
+ * @description
+ * Remove one or more attributes from a set of elements.
+ *
+ * @param attribute
+ * The name of the attribute(s) to remove. This can be a list of space-
+ * separated attribute names.
+ *
+ * @param [selector]
+ * Optional. A CSS selector for the elements to target. Default to
+ * `[attribute]`, or `[attribute1],[attribute2],...` if more than one
+ * attribute name is specified.
+ *
+ * @param [behavior]
+ * Optional. Space-separated tokens which modify the default behavior.
+ * - `asap`: Try to remove the attribute as soon as possible. Default behavior
+ * is to remove the attribute(s) asynchronously.
+ * - `stay`: Keep trying to remove the specified attribute(s) on DOM mutations.
+ * */
+
+export function removeAttr(
+ rawToken = '',
+ rawSelector = '',
+ behavior = ''
+) {
+ if ( typeof rawToken !== 'string' ) { return; }
+ if ( rawToken === '' ) { return; }
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('remove-attr', rawToken, rawSelector, behavior);
+ const tokens = safe.String_split.call(rawToken, /\s*\|\s*/);
+ const selector = tokens
+ .map(a => `${rawSelector}[${CSS.escape(a)}]`)
+ .join(',');
+ if ( safe.logLevel > 1 ) {
+ safe.uboLog(logPrefix, `Target selector:\n\t${selector}`);
+ }
+ const asap = /\basap\b/.test(behavior);
+ let timerId;
+ const rmattrAsync = ( ) => {
+ if ( timerId !== undefined ) { return; }
+ timerId = safe.onIdle(( ) => {
+ timerId = undefined;
+ rmattr();
+ }, { timeout: 17 });
+ };
+ const rmattr = ( ) => {
+ if ( timerId !== undefined ) {
+ safe.offIdle(timerId);
+ timerId = undefined;
+ }
+ try {
+ const nodes = document.querySelectorAll(selector);
+ for ( const node of nodes ) {
+ for ( const attr of tokens ) {
+ if ( node.hasAttribute(attr) === false ) { continue; }
+ node.removeAttribute(attr);
+ safe.uboLog(logPrefix, `Removed attribute '${attr}'`);
+ }
+ }
+ } catch {
+ }
+ };
+ const mutationHandler = mutations => {
+ if ( timerId !== undefined ) { return; }
+ let skip = true;
+ for ( let i = 0; i < mutations.length && skip; i++ ) {
+ const { type, addedNodes, removedNodes } = mutations[i];
+ if ( type === 'attributes' ) { skip = false; }
+ for ( let j = 0; j < addedNodes.length && skip; j++ ) {
+ if ( addedNodes[j].nodeType === 1 ) { skip = false; break; }
+ }
+ for ( let j = 0; j < removedNodes.length && skip; j++ ) {
+ if ( removedNodes[j].nodeType === 1 ) { skip = false; break; }
+ }
+ }
+ if ( skip ) { return; }
+ asap ? rmattr() : rmattrAsync();
+ };
+ const start = ( ) => {
+ rmattr();
+ if ( /\bstay\b/.test(behavior) === false ) { return; }
+ const observer = new MutationObserver(mutationHandler);
+ observer.observe(document, {
+ attributes: true,
+ attributeFilter: tokens,
+ childList: true,
+ subtree: true,
+ });
+ };
+ runAt(( ) => { start(); }, safe.String_split.call(behavior, /\s+/));
+}
+registerScriptlet(removeAttr, {
+ name: 'remove-attr.js',
+ aliases: [
+ 'ra.js',
+ ],
+ dependencies: [
+ runAt,
+ safeSelf,
+ ],
+});
+
+/******************************************************************************/