Skip to content

Latest commit

 

History

History
816 lines (631 loc) · 25.8 KB

README.md

File metadata and controls

816 lines (631 loc) · 25.8 KB

AdGuard Scriptlets and Redirect Resources

AdGuard's Scriptlets and Redirect resources library which provides extended capabilities for content blocking.


Scriptlets

Scriptlet is a JavaScript function which can be used in a declarative manner in AdGuard filtering rules.

AdGuard supports a lot of different scriptlets. Please note, that in order to achieve cross-blocker compatibility, we also support syntax of uBO and ABP.

Syntax

Blocking rules

[domains]#%#//scriptlet(name[, arguments])
  • domains — optional, a list of domains where the rule should be applied;
  • name — required, a name of the scriptlet from AdGuard Scriptlets library;
  • arguments — optional, a list of string arguments (no other types of arguments are supported).

Remarks

  • The meaning of the arguments depends on the scriptlet.

  • Special characters in scriptlet argument must be escaped properly:

    • valid:
      • 'prop["nested"]'
      • "prop['nested']"
      • 'prop[\'nested\']'
      • "prop[\"nested\"]"
    • not valid:
      • 'prop['nested']'
      • "prop["nested"]"
  • Scriptlet name and each of the arguments should be wrapped in quotes. You can use either single or double quotes for the scriptlet name and arguments. Single quote is recommended but not for cases when its usage makes readability worse, e.g. ".css('display','block');" is more preferred then '.css(\'display\',\'block\');'.

Exception rules

[domains]#@%#//scriptlet([name[, arguments]])
  • domains — optional, a list of domains where the rule should be applied;
  • name — optional, a name of the scriptlet to except from the applying; if not set, all scriptlets will not be applied;
  • arguments — optional, a list of string arguments to match the same blocking rule and disable it.

Examples

  1. Apply the abort-on-property-read scriptlet on all pages of example.org and its subdomains, and passes one argument to it (alert):

    example.org#%#//scriptlet('abort-on-property-read', 'alert')
  2. Remove the branding class from all div[class^="inner"] elements on all pages of example.org and its subdomains:

    example.org#%#//scriptlet('remove-class', 'branding', 'div[class^="inner"]')
  3. Apply set-constant and set-cookie on any webpage, but because of specific scriptlet exception rule only set-constant scriptlet will be applied on example.org and its subdomains:

    #%#//scriptlet('set-constant', 'adList', 'emptyArr')
    #%#//scriptlet('set-cookie', 'accepted', 'true')
    example.org#@%#//scriptlet('set-cookie')
  4. Apply adjust-setInterval on any webpage, and set-local-storage-item on all pages of example.com and its subdomains, but there is also multiple scriptlet exception rule, so no scriptlet rules will be applied on example.com and its subdomains:

    #%#//scriptlet('adjust-setInterval', 'count', '*', '0.001')
    example.com#%#//scriptlet('set-local-storage-item', 'ALLOW_COOKIES', 'false')
    example.com#@%#//scriptlet()

Trusted scriptlets

Trusted scriptlets are scriptlets with extended functionality. Their names are prefixed with trusted-, e.g trusted-click-element, to be easily distinguished from common scriptlets.

Restriction

Trusted scriptlets application must be restricted due to dangerous nature of their capabilities. Allowed sources of trusted scriptlets are:

  • filters created by AdGuard Team,
  • custom filters which were installed as trusted,
  • user rules.

Trusted scriptlets has no compatibility table as they are not compatible with any other blocker.

Trusted scriptlets list

Redirect resources

AdGuard is able to redirect web requests to a local "resource".

Syntax

AdGuard uses the same filtering rule syntax as uBlock Origin. Also, it is compatible with ABP $rewrite=abp-resource modifier.

$redirect is a modifier for the basic filtering rules so rules with this modifier support all other basic modifiers like $domain, $third-party, $script, etc.

The value of the $redirect modifier must be the name of the resource that will be used for redirection. See the list of available redirect resources.

Priority of $redirect rules is described in the Knowledge Base.

Examples

  • ||example.org/script.js$script,redirect=noopjs — redirects all requests to script.js to the resource named noopjs.
  • ||example.org/test.mp4$media,redirect=noopmp4-1s — requests to example.org/test.mp4 will be redirected to the resource named noopmp4-1s.

uBlock Origin specifies additional resource name none that can disable other redirect rules. AdGuard does not support it, use $badfilter to disable specific rules.


Development

How to build

Install dependencies:

yarn install

Build for CoreLibs:

yarn corelibs

Build dev (rebuild js files on every change):

yarn watch

Build for Extension

In scriptlets directory install dependencies, build scriptlets bundle, and create scriptlets link.

yarn
yarn build
yarn link

In tsurlfilter directory install and link dependencies, link scriptlets, move into package and build, and create tsurlfilter link.

lerna bootstrap

yarn link "@adguard/scriptlets"

cd ./packages/tsurlfilter
yarn build
yarn link

In extension directory install dependencies, link packages and build

yarn

yarn link @adguard/scriptlets
yarn link @adguard/tsurlfilter

yarn dev

How to test

Some tests are run in QUnit, some in Jest.

Run all tests:

yarn test
  1. QUnit is used for testing of scriptlets, redirects, and helpers:

    yarn test:qunit [scriptlets | redirects | helpers]
    

    For scriptlets and redirects test run can be more specific:

    // node test run
    yarn test:qunit scriptlets --name set-cookie
    yarn test:qunit redirects --name ati-smarttag
    
    // gui test run
    yarn test:qunit scriptlets --name set-cookie --gui
    yarn test:qunit redirects --name ati-smarttag --gui

    For debugging purposes after some test is running in gui mode, you may change your scriptlet/redirect code, and without stopping the server run in new terminal:

    yarn test:qunit scriptlets --name set-cookie --build
  2. Run all jest tests:

    yarn test:jest

    or limit the testing — testRegex may be specified in jest.config.js or specify test name in command line, e.g.:

    yarn test:jest -t isValidScriptletRule

To run browserstack tests create .env file or copy and rename .env-example.

Fill in <username> and <key> with data from your Browserstack profile. Run next command:

yarn browserstack

Tests run by jest should be named .spec.js, so they will be not included in the QUnit tests.

Debugging

Use debugger; statement where you need it, run

yarn test

and open needed HTML file from tests/dist in your browser with devtools

How to link packages

Scriptlets library relies on external packages, such as @adguard/agtree. During development, you might need to link these packages using yarn link. When you do this, make sure to install dependencies in the tsurlfilter monorepo with pnpm i --shamefully-hoist, otherwise you will encounter issues during tests and builds, because we use Babel runtime and in the nested structure, the program will try to resolve it from the node_modules folder of the linked package, not from the node_modules in the Scriptlets library.

The reason behind this is that pnpm creates a nested node_modules structure, while yarn and npm create a flat structure. By adding --shamefully-hoist, you force pnpm to create a flat structure, which is required for the Babel runtime to work correctly.

How to update wiki

There are two scripts to update wiki:

  1. yarn wiki:build-table — checks compatibility data updates and updates the compatibility table. Should be run manually while the release preparation.
  2. yarn wiki:build-docs — updates wiki pages about-scriptlets.md and about-redirects.md. They are being generated from JSDoc-type comments of corresponding scriptlets and redirects source files due to @scriptlet/@redirect and @description tags. Runs automatically while the release build.

Usage

CoreLibs

For CoreLibs usage you should use dist/scriptlets.corelibs.json and dist/redirects.json.

File example:

{
    "version": "1.0.0",
    "scriptlets": [
        {
            "names": [
                "abort-on-property-read",
                "ubo-abort-on-property-read.js",
                "abp-abort-on-property-read"
            ],
            "scriptlet": "function() { ...code... }"
        },
    ]
}

Schema:

{
    "type": "object",
    "properties": {
        "version": {
            "type": "string"
        },
        "scriptlets": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "names": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        }
                    },
                    "scriptlet": {
                        "type": "string"
                    }
                },
            }
        }
    }
}

NPM module

Installation

yarn add @adguard/scriptlets
npm install @adguard/scriptlets

API description

Scriptlets library

Scriptlets API

You are welcome to use scriptlets and redirect resources as a CJS module. They can be imported from dist/cjs/scriptlets.cjs.js:

const scriptlets = require('scriptlets');
const { redirects } = require('scriptlets');

And also there is a module at dist/scriptlets.js which has been exported to a global variable scriptlets with such methods:

Methods

invoke()
/**
 * Returns scriptlet code by `source`.
 *
 * @param {Source} source Scriptlet properties.
 *
 * @returns {string|null} Scriptlet code.
 * @throws An error on unknown scriptlet name.
 */
scriptlets.invoke(source);

where:

/**
 * @typedef {Object} Source — Scriptlet properties.
 * @property {string} name — Scriptlet name.
 * @property {Array<string>} args — Arguments for scriptlet function.
 * @property {'extension'|'corelibs'} engine — Defines the final form of scriptlet string presentation.
 * @property {string} [version] — Extension version.
 * @property {boolean} [verbose] — Flag to enable debug information printing to console.
 * @property {string} [ruleText] — Deprecated: Source rule text, needed for debug purposes.
 * @property {string} [domainName] — Domain name where scriptlet is applied, needed for debug purposes.
 * @property {string} [uniqueId] — Optional unique identifier for a scriptlet instance.
 * Used to ensure the scriptlet is executed only once per context by tracking its execution status.
 */
getScriptletFunction()
/**
 * Returns scriptlet function by `name`.
 *
 * @param {string} name Scriptlet name
 *
 * @returns {Function} — Scriptlet function.
 */
scriptlets.getScriptletFunction(name);
isValidScriptletName()
/**
 * Checks whether the `name` is valid scriptlet name.
 * Uses cache for better performance.
 *
 * @param {string} name — Scriptlet name
 * @returns {boolean} — True if scriptlet name is valid.
 */
scriptlets.isValidScriptletName(name);
isValidScriptletRule()
/**
 * 1. For ADG scriptlet checks whether the scriptlet syntax and name are valid.
 * 2. For UBO and ABP scriptlet first checks their compatibility with ADG
 * by converting them into ADG syntax, and after that checks the name.
 *
 * ADG or UBO rules are "single-scriptlet", but ABP rule may contain more than one snippet
 * so if at least one of them is not valid — whole `ruleText` rule is not valid too.
 *
 * @param {string} ruleText — Any scriptlet rule — ADG or UBO or ABP.
 *
 * @returns {boolean} — True if scriptlet name is valid in rule.
 */
scriptlets.isValidScriptletRule(ruleText);
isAdgScriptletRule(), isUboScriptletRule(), isAbpSnippetRule()
/**
 * Checks if the `rule` is AdGuard / Ubo / Abp scriptlet rule
 * @param {string} rule — any rule
 * @returns {boolean}
 */
scriptlets.isAdgScriptletRule(rule);
scriptlets.isUboScriptletRule(rule);
scriptlets.isAbpSnippetRule(rule);
convertUboToAdg()
/**
 * Converts Ubo scriptlet rule to AdGuard
 * @param {string} rule — Ubo rule
 * @returns {string[]} — array with single AdGuard scriptlet rule
 */
scriptlets.convertUboToAdg(rule);

Note that parameters in UBO rule should be separated by comma + space. Otherwise, the rule is not valid.

convertAbpToAdg()
/**
 * Converts Abp snippet rule to AdGuard
 * @param {string} rule — Abp rule
 * @returns {string[]} — array with AdGuard scriptlet rule or rules if Abp-rule has few snippets in one line
 */
scriptlets.convertAbpToAdg(rule);
convertScriptletToAdg()
/**
 * Converts any scriptlet rule into AdGuard syntax rule.
 * Comment is returned as is.
 *
 * @param {string} rule — Scriptlet rule.
 *
 * @returns {string[]} — Array of AdGuard scriptlet rules: one array item for ADG and UBO or few items for ABP.
 * For the ADG `rule`, validates its syntax and returns an empty array if it is invalid.
 */
scriptlets.convertScriptletToAdg(rule);
convertAdgToUbo()
/**
 * Converts AdGuard scriptlet rule to UBO one
 * @param {string} rule — AdGuard scriptlet rule
 * @returns {string} — UBO scriptlet rule
 */
scriptlets.convertAdgToUbo(rule);

Properties

SCRIPTLETS_VERSION

type: string

Current version of scriptlets library.

Redirects API methods

import { redirects } from '@adguard/scriptlets';
getCode()
/**
 * Returns redirects code
 * @param {Source} source
 * @returns {string}
 */
redirects.getCode(source);
isAdgRedirectRule()
/**
 * Checks whether the `rule` is AdGuard redirect resource rule.
 * Discards comments and JS rules and checks whether the `rule` has $redirect or $redirect-rule modifier
 * @param {string} rule
 */
redirects.isAdgRedirectRule(rule)
isValidAdgRedirectRule()
/**
 * Checks whether the `rule` is **valid** AdGuard redirect resource rule.
 * No matter $redirect or $redirect-rule
 * @param {string} rule
 * @returns {boolean}
 */
redirects.isValidAdgRedirectRule(rule);
isRedirectResourceCompatibleWithAdg()
/**
 * Checks if the specified redirect resource is compatible with AdGuard
 *
 * @param redirectName - Redirect resource name to check
 * @returns true if the redirect resource is compatible with AdGuard
 */
redirects.isRedirectResourceCompatibleWithAdg(redirectName);
isAdgRedirectCompatibleWithUbo()
/**
 * Checks whether the AdGuard redirect `rule` has Ubo analog.
 * Needed for Adg->Ubo conversion. No matter $redirect or $redirect-rule modifier is used
 * @param {string} rule — AdGuard rule
 * @returns {boolean} — true if the rule can be converted to Ubo syntax
 */
redirects.isAdgRedirectCompatibleWithUbo(rule);
isUboRedirectCompatibleWithAdg()
/**
 * Checks if the Ubo redirect `rule` has AdGuard analog.
 * Needed for Ubo->Adg conversion. No matter $redirect or $redirect-rule modifier is used
 * @param {string} rule — Ubo rule
 * @returns {boolean} — true if the rule can be converted to AdGuard syntax
 */
redirects.isUboRedirectCompatibleWithAdg(rule);
isAbpRedirectCompatibleWithAdg()
/**
 * Checks whether the Abp redirect `rule` has AdGuard analog. Needed for Abp->Adg conversion
 * @param {string} rule — Abp rule
 * @returns {boolean} — true if the rule can be converted to AdGuard syntax
 */
redirects.isAbpRedirectCompatibleWithAdg(rule);
convertUboRedirectToAdg()
/**
 * Converts Ubo redirect resource rule to AdGuard syntax.
 * No matter $redirect or $redirect-rule modifier is used
 * @param {string} rule — Ubo rule
 * @returns {string} — Adg rule
 */
redirects.convertUboRedirectToAdg(rule);
convertAbpRedirectToAdg()
/**
 * Converts Abp redirect resource rule to AdGuard syntax
 * @param {string} rule — Abp rule
 * @returns {string} — Adg rule
 */
redirects.convertAbpRedirectToAdg(rule);
convertRedirectToAdg()
/**
 * Checks whether the `rule` is any redirect rule and converts it to AdGuard syntax.
 * No matter $redirect or $redirect-rule modifier is used
 * @param {string} rule — any resource rule
 * @returns {string} — valid Adguard redirect resource rule
 */
redirects.convertRedirectToAdg(rule);
convertRedirectNameToAdg()
/**
 * Converts a redirect name to ADG compatible one, if possible
 *
 * @param {string} name Redirect name to convert
 * @returns {string|undefined} Converted ADG compatible redirect name or `undefined` if the redirect isn't supported
 */
redirects.convertRedirectNameToAdg(rule);
convertAdgRedirectToUbo()
/**
 * Converts Adg redirect rule to Ubo syntax.
 * No matter $redirect or $redirect-rule modifier is used
 * @param {string} rule — Adg rule
 * @returns {string} — Ubo rule
 */
redirects.convertAdgRedirectToUbo(rule);
getRedirectFilename()
/**
 * For a given name or alias of redirect returns the corresponding filename
 * @param {string} name — name or alias of redirect
 * @returns {string} — Redirect's filename with extension
 */
redirects.getRedirectFilename(name);
Redirects class
import { Redirects } from '@adguard/scriptlets';

/**
 * Converts rawYaml into JS object with sources titles used as keys
 */
const redirects = new Redirects(rawYaml)

where rawYaml is a string with YAML content located in dist/redirects.yml.

getRedirect()
/**
 * Returns redirect source object by title
 */
const redirect = redirects.getRedirect('noopjs');

/**
 * Redirect is an object with following props:
 * {
 *     title: 1x1-transparent.gif
 *     comment: http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever
 *     contentType: image/gif;base64
 *     content: R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
 * }
 */
isBlocking()
/**
 * Check if redirect is blocking, e.g. click2load.html
 */
const isBlocking = redirect.isBlocking('click2load.html');
Browser Version
Chrome ✅ 55
Firefox ✅ 52
Edge ✅ 15
Opera ✅ 42
Safari ✅ 11
Internet Explorer