diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 05:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 05:47:55 +0000 |
commit | 31d6ff6f931696850c348007241195ab3b2eddc7 (patch) | |
tree | 615cb1c57ce9f6611bad93326b9105098f379609 /assets | |
parent | Initial commit. (diff) | |
download | ublock-origin-31d6ff6f931696850c348007241195ab3b2eddc7.tar.xz ublock-origin-31d6ff6f931696850c348007241195ab3b2eddc7.zip |
Adding upstream version 1.55.0+dfsg.upstream/1.55.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'assets')
-rw-r--r-- | assets/assets.dev.json | 902 | ||||
-rw-r--r-- | assets/assets.json | 903 | ||||
-rw-r--r-- | assets/resources/scriptlets.js | 4376 |
3 files changed, 6181 insertions, 0 deletions
diff --git a/assets/assets.dev.json b/assets/assets.dev.json new file mode 100644 index 0000000..129cc75 --- /dev/null +++ b/assets/assets.dev.json @@ -0,0 +1,902 @@ +{ + "assets.json": { + "content": "internal", + "updateAfter": 13, + "contentURL": [ + "https://raw.githubusercontent.com/gorhill/uBlock/master/assets/assets.dev.json", + "assets/assets.dev.json" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/ublock/assets.dev.json", + "https://ublockorigin.pages.dev/ublock/assets.dev.json", + "https://cdn.jsdelivr.net/gh/gorhill/uBlock@master/assets/assets.dev.json", + "https://cdn.statically.io/gh/gorhill/uBlock/master/assets/assets.dev.json" + ] + }, + "public_suffix_list.dat": { + "content": "internal", + "updateAfter": 19, + "contentURL": [ + "https://publicsuffix.org/list/public_suffix_list.dat", + "assets/thirdparties/publicsuffix.org/list/effective_tld_names.dat" + ] + }, + "ublock-badlists": { + "content": "internal", + "updateAfter": 29, + "contentURL": [ + "https://ublockorigin.github.io/uAssets/filters/badlists.txt", + "assets/ublock/badlists.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/badlists.txt", + "https://ublockorigin.pages.dev/filters/badlists.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/badlists.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/badlists.txt" + ] + }, + "ublock-filters": { + "content": "filters", + "group": "default", + "parent": "uBlock filters", + "title": "uBlock filters – Ads", + "tags": "ads", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/filters/filters.txt", + "assets/ublock/filters.min.txt", + "assets/ublock/filters.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/filters.min.txt", + "https://ublockorigin.pages.dev/filters/filters.min.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/filters.min.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/filters.min.txt" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets" + }, + "ublock-badware": { + "content": "filters", + "group": "default", + "parent": "uBlock filters", + "title": "uBlock filters – Badware risks", + "tags": "malware security", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/filters/badware.txt", + "assets/ublock/badware.min.txt", + "assets/ublock/badware.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/badware.min.txt", + "https://ublockorigin.pages.dev/filters/badware.min.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/badware.min.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/badware.min.txt" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets", + "instructionURL": "https://github.com/gorhill/uBlock/wiki/Badware-risks" + }, + "ublock-privacy": { + "content": "filters", + "group": "default", + "parent": "uBlock filters", + "title": "uBlock filters – Privacy", + "tags": "privacy", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/filters/privacy.txt", + "assets/ublock/privacy.min.txt", + "assets/ublock/privacy.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/privacy.min.txt", + "https://ublockorigin.pages.dev/filters/privacy.min.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/privacy.min.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/privacy.min.txt" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets" + }, + "ublock-unbreak": { + "content": "filters", + "group": "default", + "parent": "uBlock filters", + "title": "uBlock filters – Unbreak", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/filters/unbreak.txt", + "assets/ublock/unbreak.min.txt", + "assets/ublock/unbreak.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/unbreak.min.txt", + "https://ublockorigin.pages.dev/filters/unbreak.min.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/unbreak.min.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/unbreak.min.txt" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets" + }, + "ublock-quick-fixes": { + "content": "filters", + "group": "default", + "parent": "uBlock filters", + "title": "uBlock filters – Quick fixes", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/filters/quick-fixes.txt", + "assets/ublock/quick-fixes.min.txt", + "assets/ublock/quick-fixes.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/quick-fixes.min.txt", + "https://ublockorigin.pages.dev/filters/quick-fixes.min.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/quick-fixes.min.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/quick-fixes.min.txt?dev=1" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets" + }, + "adguard-generic": { + "content": "filters", + "group": "ads", + "off": true, + "title": "AdGuard – Ads", + "tags": "ads", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/2_without_easylist.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "adguard-mobile": { + "content": "filters", + "group": "ads", + "off": true, + "title": "AdGuard – Mobile Ads", + "tags": "ads mobile", + "ua": "mobile", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/11.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "easylist": { + "content": "filters", + "group": "ads", + "title": "EasyList", + "tags": "ads", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/thirdparties/easylist.txt", + "assets/thirdparties/easylist/easylist.txt", + "assets/thirdparties/easylist-downloads.adblockplus.org/easylist.txt" + ], + "cdnURLs": [ + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/thirdparties/easylist.txt", + "https://ublockorigin.pages.dev/thirdparties/easylist.txt" + ], + "supportURL": "https://easylist.to/" + }, + "adguard-spyware-url": { + "content": "filters", + "group": "privacy", + "off": true, + "title": "AdGuard URL Tracking Protection", + "tags": "privacy", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/17.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "adguard-spyware": { + "content": "filters", + "group": "privacy", + "off": true, + "title": "AdGuard Tracking Protection", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/3.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "block-lan": { + "content": "filters", + "group": "privacy", + "off": true, + "title": "Block Outsider Intrusion into LAN", + "tags": "privacy security", + "contentURL": "https://ublockorigin.github.io/uAssets/filters/lan-block.txt", + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/lan-block.txt", + "https://ublockorigin.pages.dev/filters/lan-block.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/lan-block.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/lan-block.txt" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets" + }, + "easyprivacy": { + "content": "filters", + "group": "privacy", + "title": "EasyPrivacy", + "tags": "privacy", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/thirdparties/easyprivacy.txt", + "assets/thirdparties/easylist/easyprivacy.txt", + "assets/thirdparties/easylist-downloads.adblockplus.org/easyprivacy.txt" + ], + "cdnURLs": [ + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easyprivacy.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/thirdparties/easyprivacy.txt", + "https://ublockorigin.pages.dev/thirdparties/easyprivacy.txt" + ], + "supportURL": "https://easylist.to/" + }, + "urlhaus-1": { + "content": "filters", + "group": "malware", + "title": "Online Malicious URL Blocklist", + "contentURL": [ + "https://malware-filter.gitlab.io/urlhaus-filter/urlhaus-filter-ag-online.txt", + "assets/thirdparties/urlhaus-filter/urlhaus-filter-online.txt" + ], + "cdnURLs": [ + "https://curbengh.github.io/malware-filter/urlhaus-filter-ag-online.txt", + "https://malware-filter.gitlab.io/urlhaus-filter/urlhaus-filter-ag-online.txt", + "https://malware-filter.pages.dev/urlhaus-filter-ag-online.txt" + ], + "supportURL": "https://gitlab.com/malware-filter/urlhaus-filter#malicious-url-blocklist" + }, + "curben-phishing": { + "content": "filters", + "group": "malware", + "off": true, + "title": "Phishing URL Blocklist", + "contentURL": "https://malware-filter.gitlab.io/phishing-filter/phishing-filter.txt", + "cdnURLs": [ + "https://curbengh.github.io/phishing-filter/phishing-filter.txt", + "https://malware-filter.gitlab.io/phishing-filter/phishing-filter.txt", + "https://phishing-filter.pages.dev/phishing-filter.txt" + ], + "supportURL": "https://gitlab.com/malware-filter/phishing-filter#phishing-url-blocklist" + }, + "adguard-social": { + "content": "filters", + "group": "annoyances", + "parent": "AdGuard – Annoyances", + "off": true, + "title": "AdGuard – Social Media", + "tags": "annoyances social", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/4.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "adguard-cookies": { + "content": "filters", + "group": "annoyances", + "parent": "AdGuard – Annoyances|AdGuard/uBO – Cookie Notices", + "off": true, + "title": "AdGuard – Cookie Notices", + "tags": "annoyances cookies", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/18.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "ublock-cookies-adguard": { + "content": "filters", + "group": "annoyances", + "parent": "AdGuard – Annoyances|AdGuard/uBO – Cookie Notices", + "off": true, + "title": "uBlock filters – Cookie Notices", + "tags": "annoyances cookies", + "contentURL": "https://ublockorigin.github.io/uAssets/filters/annoyances-cookies.txt", + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/annoyances-cookies.txt", + "https://ublockorigin.pages.dev/filters/annoyances-cookies.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/annoyances-cookies.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/annoyances-cookies.txt" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets" + }, + "adguard-popup-overlays": { + "content": "filters", + "group": "annoyances", + "parent": "AdGuard – Annoyances", + "off": true, + "title": "AdGuard – Popup Overlays", + "tags": "annoyances", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/19.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "adguard-mobile-app-banners": { + "content": "filters", + "group": "annoyances", + "parent": "AdGuard – Annoyances", + "off": true, + "title": "AdGuard – Mobile App Banners", + "tags": "annoyances mobile", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/20.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "adguard-other-annoyances": { + "content": "filters", + "group": "annoyances", + "parent": "AdGuard – Annoyances", + "off": true, + "title": "AdGuard – Other Annoyances", + "tags": "annoyances", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/21.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "adguard-widgets": { + "content": "filters", + "group": "annoyances", + "parent": "AdGuard – Annoyances", + "off": true, + "title": "AdGuard – Widgets", + "tags": "annoyances", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/22.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "fanboy-thirdparty_social": { + "content": "filters", + "group": "annoyances", + "off": true, + "title": "Fanboy – Anti-Facebook", + "tags": "privacy", + "contentURL": "https://secure.fanboy.co.nz/fanboy-antifacebook.txt", + "supportURL": "https://github.com/ryanbr/fanboy-adblock/issues" + }, + "easylist-annoyances": { + "content": "filters", + "group": "annoyances", + "parent": "EasyList – Annoyances", + "off": true, + "title": "EasyList – Other Annoyances", + "tags": "annoyances", + "contentURL": "https://ublockorigin.github.io/uAssets/thirdparties/easylist-annoyances.txt", + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-annoyances.txt", + "https://ublockorigin.pages.dev/thirdparties/easylist-annoyances.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-annoyances.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/thirdparties/easylist-annoyances.txt" + ], + "supportURL": "https://github.com/easylist/easylist#fanboy-lists" + }, + "easylist-chat": { + "content": "filters", + "group": "annoyances", + "parent": "EasyList – Annoyances", + "off": true, + "title": "EasyList – Chat Widgets", + "tags": "annoyances", + "contentURL": "https://ublockorigin.github.io/uAssets/thirdparties/easylist-chat.txt", + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-chat.txt", + "https://ublockorigin.pages.dev/thirdparties/easylist-chat.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-chat.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/thirdparties/easylist-chat.txt" + ], + "supportURL": "https://github.com/easylist/easylist#fanboy-lists" + }, + "fanboy-cookiemonster": { + "content": "filters", + "group": "annoyances", + "parent": "EasyList – Annoyances|EasyList/uBO – Cookie Notices", + "off": true, + "title": "EasyList – Cookie Notices", + "tags": "annoyances cookies", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/thirdparties/easylist-cookies.txt", + "https://secure.fanboy.co.nz/fanboy-cookiemonster_ubo.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-cookies.txt", + "https://ublockorigin.pages.dev/thirdparties/easylist-cookies.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-cookies.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/thirdparties/easylist-cookies.txt", + "https://secure.fanboy.co.nz/fanboy-cookiemonster_ubo.txt" + ], + "supportURL": "https://github.com/easylist/easylist#fanboy-lists" + }, + "ublock-cookies-easylist": { + "content": "filters", + "group": "annoyances", + "parent": "EasyList – Annoyances|EasyList/uBO – Cookie Notices", + "off": true, + "title": "uBlock filters – Cookie Notices", + "tags": "annoyances cookies", + "contentURL": "https://ublockorigin.github.io/uAssets/filters/annoyances-cookies.txt", + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/annoyances-cookies.txt", + "https://ublockorigin.pages.dev/filters/annoyances-cookies.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/annoyances-cookies.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/annoyances-cookies.txt" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets" + }, + "easylist-newsletters": { + "content": "filters", + "group": "annoyances", + "parent": "EasyList – Annoyances", + "off": true, + "title": "EasyList – Newsletter Notices", + "tags": "annoyances", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/thirdparties/easylist-newsletters.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-newsletters.txt", + "https://ublockorigin.pages.dev/thirdparties/easylist-newsletters.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-newsletters.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/thirdparties/easylist-newsletters.txt" + ], + "supportURL": "https://easylist.to/" + }, + "easylist-notifications": { + "content": "filters", + "group": "annoyances", + "parent": "EasyList – Annoyances", + "off": true, + "title": "EasyList – Notifications", + "tags": "annoyances", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/thirdparties/easylist-notifications.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-notifications.txt", + "https://ublockorigin.pages.dev/thirdparties/easylist-notifications.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-notifications.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/thirdparties/easylist-notifications.txt" + ], + "supportURL": "https://easylist.to/" + }, + "fanboy-social": { + "content": "filters", + "group": "annoyances", + "parent": "EasyList – Annoyances", + "off": true, + "title": "EasyList – Social Widgets", + "tags": "annoyances social", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/thirdparties/easylist-social.txt", + "https://secure.fanboy.co.nz/fanboy-social_ubo.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-social.txt", + "https://ublockorigin.pages.dev/thirdparties/easylist-social.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-social.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/thirdparties/easylist-social.txt", + "https://secure.fanboy.co.nz/fanboy-social_ubo.txt" + ], + "supportURL": "https://easylist.to/" + }, + "ublock-annoyances": { + "content": "filters", + "group": "annoyances", + "off": true, + "title": "uBlock filters – Annoyances", + "tags": "annoyances", + "contentURL": "https://ublockorigin.github.io/uAssets/filters/annoyances.txt", + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/annoyances.min.txt", + "https://ublockorigin.pages.dev/filters/annoyances.min.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/annoyances.min.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/annoyances.min.txt" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets" + }, + "dpollock-0": { + "content": "filters", + "group": "multipurpose", + "updateAfter": 13, + "off": true, + "title": "Dan Pollock’s hosts file", + "tags": "ads privacy security", + "contentURL": "https://someonewhocares.org/hosts/hosts", + "supportURL": "https://someonewhocares.org/hosts/" + }, + "plowe-0": { + "content": "filters", + "group": "multipurpose", + "updateAfter": 13, + "title": "Peter Lowe’s Ad and tracking server list", + "tags": "ads privacy security", + "contentURL": [ + "https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=1&mimetype=plaintext", + "assets/thirdparties/pgl.yoyo.org/as/serverlist.txt", + "assets/thirdparties/pgl.yoyo.org/as/serverlist" + ], + "supportURL": "https://pgl.yoyo.org/adservers/" + }, + "ALB-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇦🇱al 🇽🇰xk: Adblock List for Albania", + "tags": "ads albania shqipja", + "lang": "sq", + "contentURL": "https://raw.githubusercontent.com/AnXh3L0/blocklist/master/albanian-easylist-addition/Albania.txt", + "supportURL": "https://github.com/AnXh3L0/blocklist" + }, + "ara-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇪🇬eg 🇸🇦sa 🇲🇦ma 🇩🇿dz: Liste AR", + "tags": "ads arabic اَلْعَرَبِيَّةُ", + "lang": "ar", + "contentURL": "https://easylist-downloads.adblockplus.org/Liste_AR.txt", + "supportURL": "https://forums.lanik.us/viewforum.php?f=98" + }, + "BGR-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇧🇬bg: Bulgarian Adblock list", + "tags": "ads bulgarian България macedonian Македонија", + "lang": "bg mk", + "contentURL": "https://stanev.org/abp/adblock_bg.txt", + "supportURL": "https://stanev.org/abp/" + }, + "CHN-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇨🇳cn 🇹🇼tw: AdGuard Chinese (中文)", + "tags": "ads chinese 中文", + "lang": "ug zh", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/224.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters" + }, + "CZE-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇨🇿cz 🇸🇰sk: EasyList Czech and Slovak", + "tags": "ads czech česká slovak slovenská", + "lang": "cs sk", + "contentURL": "https://raw.githubusercontent.com/tomasko126/easylistczechandslovak/master/filters.txt", + "supportURL": "https://github.com/tomasko126/easylistczechandslovak" + }, + "DEU-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇩🇪de 🇨🇭ch 🇦🇹at: EasyList Germany", + "tags": "ads german deutschland luxembourgish lëtzebuerg romansh", + "lang": "de dsb hsb lb rm", + "contentURL": [ + "https://easylist.to/easylistgermany/easylistgermany.txt", + "https://easylist-downloads.adblockplus.org/easylistgermany.txt" + ], + "supportURL": "https://forums.lanik.us/viewforum.php?f=90" + }, + "EST-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇪🇪ee: Eesti saitidele kohandatud filter", + "tags": "ads estonian", + "lang": "et", + "contentURL": "https://adblock.ee/list.php", + "supportURL": "https://adblock.ee/" + }, + "FIN-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇫🇮fi: Adblock List for Finland", + "tags": "ads finnish", + "lang": "fi", + "contentURL": "https://raw.githubusercontent.com/finnish-easylist-addition/finnish-easylist-addition/gh-pages/Finland_adb.txt", + "supportURL": "https://github.com/finnish-easylist-addition/finnish-easylist-addition" + }, + "FRA-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇫🇷fr 🇨🇦ca: AdGuard Français", + "tags": "ads french", + "lang": "ar br ff fr lb oc son", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/16.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters" + }, + "GRC-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇬🇷gr 🇨🇾cy: Greek AdBlock Filter", + "tags": "ads greek", + "lang": "el", + "contentURL": "https://www.void.gr/kargig/void-gr-filters.txt", + "supportURL": "https://github.com/kargig/greek-adblockplus-filter" + }, + "HRV-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇭🇷hr 🇷🇸rs: Dandelion Sprout's Serbo-Croatian filters", + "tags": "ads croatian serbian", + "lang": "hr sr", + "contentURL": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/SerboCroatianList.txt", + "supportURL": "https://github.com/DandelionSprout/adfilt#readme" + }, + "HUN-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇭🇺hu: hufilter", + "tags": "ads hungarian", + "lang": "hu", + "contentURL": "https://raw.githubusercontent.com/hufilter/hufilter/master/hufilter-ublock.txt", + "supportURL": "https://github.com/hufilter/hufilter" + }, + "IDN-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇮🇩id 🇲🇾my: ABPindo", + "tags": "ads indonesian malay", + "lang": "id ms", + "contentURL": "https://raw.githubusercontent.com/ABPindo/indonesianadblockrules/master/subscriptions/abpindo.txt", + "supportURL": "https://github.com/ABPindo/indonesianadblockrules" + }, + "IND-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇮🇳in 🇱🇰lk 🇳🇵np: IndianList", + "tags": "ads assamese bengali gujarati hindi kannada malayalam marathi nepali punjabi sinhala tamil telugu", + "lang": "as bn gu hi kn ml mr ne pa si ta te", + "contentURL": "https://easylist-downloads.adblockplus.org/indianlist.txt", + "supportURL": "https://github.com/mediumkreation/IndianList" + }, + "IRN-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇮🇷ir: PersianBlocker", + "tags": "ads af ir persian pashto tajik tj", + "lang": "fa ps tg", + "contentURL": [ + "https://raw.githubusercontent.com/MasterKia/PersianBlocker/main/PersianBlocker.txt", + "https://cdn.statically.io/gh/MasterKia/PersianBlocker/main/PersianBlocker.txt", + "https://raw.githubusercontent.com/MasterKia/PersianBlocker/main/PersianBlocker.txt" + ], + "cdnURLs": [ + "https://cdn.jsdelivr.net/gh/MasterKia/PersianBlocker@main/PersianBlocker.txt", + "https://cdn.statically.io/gh/MasterKia/PersianBlocker/main/PersianBlocker.txt" + ], + "supportURL": "https://github.com/MasterKia/PersianBlocker" + }, + "ISL-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇮🇸is: Icelandic ABP List", + "tags": "ads icelandic", + "lang": "is", + "contentURL": "https://adblock.gardar.net/is.abp.txt", + "supportURL": "https://adblock.gardar.net/" + }, + "ISR-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇮🇱il: EasyList Hebrew", + "tags": "ads hebrew", + "lang": "he", + "contentURL": "https://raw.githubusercontent.com/easylist/EasyListHebrew/master/EasyListHebrew.txt", + "supportURL": "https://github.com/easylist/EasyListHebrew" + }, + "ITA-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇮🇹it: EasyList Italy", + "tags": "ads italian", + "lang": "it lij", + "contentURL": "https://easylist-downloads.adblockplus.org/easylistitaly.txt", + "supportURL": "https://forums.lanik.us/viewforum.php?f=96" + }, + "JPN-1": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇯🇵jp: AdGuard Japanese", + "tags": "ads japanese 日本語", + "lang": "ja", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/7.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "KOR-1": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇰🇷kr: List-KR", + "tags": "ads korean 한국어", + "lang": "ko", + "contentURL": "https://cdn.jsdelivr.net/gh/List-KR/List-KR@latest/filter-uBlockOrigin.txt", + "supportURL": "https://github.com/List-KR/List-KR#readme" + }, + "LTU-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇱🇹lt: EasyList Lithuania", + "tags": "ads lithuanian", + "lang": "lt", + "contentURL": "https://raw.githubusercontent.com/EasyList-Lithuania/easylist_lithuania/master/easylistlithuania.txt", + "cdnURLs": [ + "https://cdn.jsdelivr.net/gh/EasyList-Lithuania/easylist_lithuania@master/easylistlithuania.txt", + "https://cdn.statically.io/gh/EasyList-Lithuania/easylist_lithuania/master/easylistlithuania.txt" + ], + "supportURL": "https://github.com/EasyList-Lithuania/easylist_lithuania" + }, + "LVA-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇱🇻lv: Latvian List", + "tags": "ads latvian", + "lang": "lv", + "contentURL": "https://raw.githubusercontent.com/Latvian-List/adblock-latvian/master/lists/latvian-list.txt", + "supportURL": "https://github.com/Latvian-List/adblock-latvian" + }, + "MKD-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇲🇰mk: Macedonian adBlock Filters", + "tags": "ads macedonian", + "lang": "mk", + "contentURL": "https://raw.githubusercontent.com/DeepSpaceHarbor/Macedonian-adBlock-Filters/master/Filters", + "supportURL": "https://github.com/DeepSpaceHarbor/Macedonian-adBlock-Filters" + }, + "NLD-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇳🇱nl 🇧🇪be: EasyDutch", + "tags": "ads afrikaans be belgië frisian dutch flemish nederlands netherlands nl sr suriname za", + "lang": "af fy nl", + "contentURL": "https://easydutch-ubo.github.io/EasyDutch/EasyDutch.txt", + "cdnURLs": [ + "https://easydutch-ubo.github.io/EasyDutch/EasyDutch.all.txt", + "https://cdn.jsdelivr.net/gh/EasyDutch-uBO/EasyDutch@gh-pages/EasyDutch.all.txt", + "https://cdn.statically.io/gh/EasyDutch-uBO/EasyDutch/gh-pages/EasyDutch.all.txt" + ], + "supportURL": "https://github.com/EasyDutch-uBO/EasyDutch/" + }, + "NOR-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇳🇴no 🇩🇰dk 🇮🇸is: Dandelion Sprouts nordiske filtre", + "tags": "ads norwegian danish icelandic", + "lang": "nb nn no da is", + "contentURL": [ + "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/NorwegianList.txt" + ], + "cdnURLs": [ + "https://cdn.jsdelivr.net/gh/DandelionSprout/adfilt@master/NorwegianList.txt", + "https://cdn.statically.io/gl/DandelionSprout/adfilt/master/NorwegianList.txt" + ], + "supportURL": "https://github.com/DandelionSprout/adfilt" + }, + "POL-0": { + "content": "filters", + "group": "regions", + "parent": "🇵🇱pl: Oficjalne Polskie Filtry", + "off": true, + "title": "🇵🇱pl: Oficjalne Polskie Filtry do uBlocka Origin", + "tags": "ads polish polski", + "lang": "szl pl", + "contentURL": "https://raw.githubusercontent.com/MajkiIT/polish-ads-filter/master/polish-adblock-filters/adblock.txt", + "supportURL": "https://github.com/MajkiIT/polish-ads-filter/issues", + "instructionURL": "https://github.com/MajkiIT/polish-ads-filter#polish-filters-for-adblock-ublock-origin--adguard" + }, + "POL-2": { + "content": "filters", + "group": "regions", + "parent": "🇵🇱pl: Oficjalne Polskie Filtry", + "off": true, + "title": "🇵🇱pl: Oficjalne polskie filtry przeciwko alertom o Adblocku", + "tags": "ads polish polski", + "lang": "szl pl", + "contentURL": "https://raw.githubusercontent.com/olegwukr/polish-privacy-filters/master/anti-adblock.txt", + "supportURL": "https://github.com/olegwukr/polish-privacy-filters/issues" + }, + "ROU-1": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇷🇴ro 🇲🇩md: Romanian Ad (ROad) Block List Light", + "tags": "ads romanian română moldavian moldovenească молдовеняскэ", + "lang": "ro", + "contentURL": [ + "https://raw.githubusercontent.com/tcptomato/ROad-Block/master/road-block-filters-light.txt" + ], + "supportURL": "https://github.com/tcptomato/ROad-Block" + }, + "RUS-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇷🇺ru 🇺🇦ua 🇺🇿uz 🇰🇿kz: RU AdList", + "tags": "ads belarusian беларуская kazakh tatar russian русский ukrainian українська uzbek", + "lang": "be kk tt ru uk uz", + "contentURL": "https://raw.githubusercontent.com/easylist/ruadlist/master/RuAdList-uBO.txt", + "cdnURLs": [ + "https://cdn.jsdelivr.net/gh/dimisa-RUAdList/RUAdListCDN@main/lists/ruadlist.ubo.min.txt", + "https://cdn.statically.io/gh/dimisa-RUAdList/RUAdListCDN/main/lists/ruadlist.ubo.min.txt", + "https://raw.githubusercontent.com/dimisa-RUAdList/RUAdListCDN/main/lists/ruadlist.ubo.min.txt" + ], + "supportURL": "https://forums.lanik.us/viewforum.php?f=102", + "instructionURL": "https://forums.lanik.us/viewtopic.php?f=102&t=22512" + }, + "spa-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇪🇸es 🇦🇷ar 🇲🇽mx 🇨🇴co: EasyList Spanish", + "tags": "ads aragonese basque catalan spanish español galician guarani", + "lang": "an ast ca cak es eu gl gn trs quz", + "contentURL": "https://easylist-downloads.adblockplus.org/easylistspanish.txt", + "supportURL": "https://forums.lanik.us/viewforum.php?f=103" + }, + "spa-1": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇪🇸es 🇦🇷ar 🇧🇷br 🇵🇹pt: AdGuard Spanish/Portuguese", + "tags": "ads aragonese basque catalan spanish español galician guarani portuguese português", + "lang": "an ast ca cak es eu gl gn trs pt quz", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/9.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "SVN-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇸🇮si: Slovenian List", + "tags": "ads slovenian slovenski", + "lang": "sl", + "contentURL": "https://raw.githubusercontent.com/betterwebleon/slovenian-list/master/filters.txt", + "supportURL": "https://github.com/betterwebleon/slovenian-list" + }, + "SWE-1": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇸🇪se: Frellwit's Swedish Filter", + "tags": "ads swedish svenska", + "lang": "sv", + "contentURL": "https://raw.githubusercontent.com/lassekongo83/Frellwits-filter-lists/master/Frellwits-Swedish-Filter.txt", + "supportURL": "https://github.com/lassekongo83/Frellwits-filter-lists" + }, + "THA-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇹🇭th: EasyList Thailand", + "tags": "ads thai ไทย", + "lang": "th", + "contentURL": "https://raw.githubusercontent.com/easylist-thailand/easylist-thailand/master/subscription/easylist-thailand.txt", + "supportURL": "https://github.com/easylist-thailand/easylist-thailand" + }, + "TUR-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇹🇷tr: AdGuard Turkish", + "tags": "ads turkish türkçe", + "lang": "tr", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/13.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "VIE-1": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇻🇳vn: ABPVN List", + "tags": "ads vietnamese việt", + "lang": "vi", + "contentURL": "https://raw.githubusercontent.com/abpvn/abpvn/master/filter/abpvn_ublock.txt", + "supportURL": "https://abpvn.com/" + } +} diff --git a/assets/assets.json b/assets/assets.json new file mode 100644 index 0000000..7773314 --- /dev/null +++ b/assets/assets.json @@ -0,0 +1,903 @@ +{ + "assets.json": { + "content": "internal", + "updateAfter": 13, + "contentURL": [ + "https://raw.githubusercontent.com/gorhill/uBlock/master/assets/assets.json", + "assets/assets.json" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/ublock/assets.json", + "https://ublockorigin.pages.dev/ublock/assets.json", + "https://cdn.jsdelivr.net/gh/gorhill/uBlock@master/assets/assets.json", + "https://cdn.statically.io/gh/gorhill/uBlock/master/assets/assets.json" + ] + }, + "public_suffix_list.dat": { + "content": "internal", + "updateAfter": 19, + "contentURL": [ + "https://publicsuffix.org/list/public_suffix_list.dat", + "assets/thirdparties/publicsuffix.org/list/effective_tld_names.dat" + ] + }, + "ublock-badlists": { + "content": "internal", + "updateAfter": 29, + "contentURL": [ + "https://ublockorigin.github.io/uAssets/filters/badlists.txt", + "assets/ublock/badlists.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/badlists.txt", + "https://ublockorigin.pages.dev/filters/badlists.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/badlists.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/badlists.txt" + ] + }, + "ublock-filters": { + "content": "filters", + "group": "default", + "parent": "uBlock filters", + "title": "uBlock filters – Ads", + "tags": "ads", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/filters/filters.txt", + "assets/ublock/filters.min.txt", + "assets/ublock/filters.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/filters.min.txt", + "https://ublockorigin.pages.dev/filters/filters.min.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/filters.min.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/filters.min.txt" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets" + }, + "ublock-badware": { + "content": "filters", + "group": "default", + "parent": "uBlock filters", + "title": "uBlock filters – Badware risks", + "tags": "malware security", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/filters/badware.txt", + "assets/ublock/badware.min.txt", + "assets/ublock/badware.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/badware.min.txt", + "https://ublockorigin.pages.dev/filters/badware.min.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/badware.min.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/badware.min.txt" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets", + "instructionURL": "https://github.com/gorhill/uBlock/wiki/Badware-risks" + }, + "ublock-privacy": { + "content": "filters", + "group": "default", + "parent": "uBlock filters", + "title": "uBlock filters – Privacy", + "tags": "privacy", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/filters/privacy.txt", + "assets/ublock/privacy.min.txt", + "assets/ublock/privacy.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/privacy.min.txt", + "https://ublockorigin.pages.dev/filters/privacy.min.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/privacy.min.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/privacy.min.txt" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets" + }, + "ublock-unbreak": { + "content": "filters", + "group": "default", + "parent": "uBlock filters", + "title": "uBlock filters – Unbreak", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/filters/unbreak.txt", + "assets/ublock/unbreak.min.txt", + "assets/ublock/unbreak.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/unbreak.min.txt", + "https://ublockorigin.pages.dev/filters/unbreak.min.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/unbreak.min.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/unbreak.min.txt" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets" + }, + "ublock-quick-fixes": { + "content": "filters", + "group": "default", + "parent": "uBlock filters", + "title": "uBlock filters – Quick fixes", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/filters/quick-fixes.txt", + "assets/ublock/quick-fixes.min.txt", + "assets/ublock/quick-fixes.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/quick-fixes.min.txt", + "https://ublockorigin.pages.dev/filters/quick-fixes.min.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/quick-fixes.min.txt" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets" + }, + "adguard-generic": { + "content": "filters", + "group": "ads", + "off": true, + "title": "AdGuard – Ads", + "tags": "ads", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/2_without_easylist.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "adguard-mobile": { + "content": "filters", + "group": "ads", + "off": true, + "title": "AdGuard – Mobile Ads", + "tags": "ads mobile", + "ua": "mobile", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/11.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "easylist": { + "content": "filters", + "group": "ads", + "title": "EasyList", + "tags": "ads", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/thirdparties/easylist.txt", + "assets/thirdparties/easylist/easylist.txt", + "assets/thirdparties/easylist-downloads.adblockplus.org/easylist.txt" + ], + "cdnURLs": [ + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/thirdparties/easylist.txt", + "https://ublockorigin.pages.dev/thirdparties/easylist.txt", + "https://easylist.to/easylist/easylist.txt" + ], + "supportURL": "https://easylist.to/" + }, + "adguard-spyware-url": { + "content": "filters", + "group": "privacy", + "off": true, + "title": "AdGuard URL Tracking Protection", + "tags": "privacy", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/17.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "adguard-spyware": { + "content": "filters", + "group": "privacy", + "off": true, + "title": "AdGuard Tracking Protection", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/3.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "block-lan": { + "content": "filters", + "group": "privacy", + "off": true, + "title": "Block Outsider Intrusion into LAN", + "tags": "privacy security", + "contentURL": "https://ublockorigin.github.io/uAssets/filters/lan-block.txt", + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/lan-block.txt", + "https://ublockorigin.pages.dev/filters/lan-block.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/lan-block.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/lan-block.txt" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets" + }, + "easyprivacy": { + "content": "filters", + "group": "privacy", + "title": "EasyPrivacy", + "tags": "privacy", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/thirdparties/easyprivacy.txt", + "assets/thirdparties/easylist/easyprivacy.txt", + "assets/thirdparties/easylist-downloads.adblockplus.org/easyprivacy.txt" + ], + "cdnURLs": [ + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easyprivacy.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/thirdparties/easyprivacy.txt", + "https://ublockorigin.pages.dev/thirdparties/easyprivacy.txt", + "https://easylist.to/easylist/easyprivacy.txt" + ], + "supportURL": "https://easylist.to/" + }, + "urlhaus-1": { + "content": "filters", + "group": "malware", + "title": "Online Malicious URL Blocklist", + "contentURL": [ + "https://malware-filter.gitlab.io/urlhaus-filter/urlhaus-filter-ag-online.txt", + "assets/thirdparties/urlhaus-filter/urlhaus-filter-online.txt" + ], + "cdnURLs": [ + "https://curbengh.github.io/malware-filter/urlhaus-filter-ag-online.txt", + "https://malware-filter.gitlab.io/urlhaus-filter/urlhaus-filter-ag-online.txt", + "https://malware-filter.pages.dev/urlhaus-filter-ag-online.txt" + ], + "supportURL": "https://gitlab.com/malware-filter/urlhaus-filter#malicious-url-blocklist" + }, + "curben-phishing": { + "content": "filters", + "group": "malware", + "off": true, + "title": "Phishing URL Blocklist", + "contentURL": "https://malware-filter.gitlab.io/phishing-filter/phishing-filter.txt", + "cdnURLs": [ + "https://curbengh.github.io/phishing-filter/phishing-filter.txt", + "https://malware-filter.gitlab.io/phishing-filter/phishing-filter.txt", + "https://phishing-filter.pages.dev/phishing-filter.txt" + ], + "supportURL": "https://gitlab.com/malware-filter/phishing-filter#phishing-url-blocklist" + }, + "adguard-social": { + "content": "filters", + "group": "annoyances", + "parent": "AdGuard – Annoyances", + "off": true, + "title": "AdGuard – Social Media", + "tags": "annoyances social", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/4.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "adguard-cookies": { + "content": "filters", + "group": "annoyances", + "parent": "AdGuard – Annoyances|AdGuard/uBO – Cookie Notices", + "off": true, + "title": "AdGuard – Cookie Notices", + "tags": "annoyances cookies", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/18.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "ublock-cookies-adguard": { + "content": "filters", + "group": "annoyances", + "parent": "AdGuard – Annoyances|AdGuard/uBO – Cookie Notices", + "off": true, + "title": "uBlock filters – Cookie Notices", + "tags": "annoyances cookies", + "contentURL": "https://ublockorigin.github.io/uAssets/filters/annoyances-cookies.txt", + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/annoyances-cookies.txt", + "https://ublockorigin.pages.dev/filters/annoyances-cookies.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/annoyances-cookies.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/annoyances-cookies.txt" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets" + }, + "adguard-popup-overlays": { + "content": "filters", + "group": "annoyances", + "parent": "AdGuard – Annoyances", + "off": true, + "title": "AdGuard – Popup Overlays", + "tags": "annoyances", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/19.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "adguard-mobile-app-banners": { + "content": "filters", + "group": "annoyances", + "parent": "AdGuard – Annoyances", + "off": true, + "title": "AdGuard – Mobile App Banners", + "tags": "annoyances mobile", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/20.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "adguard-other-annoyances": { + "content": "filters", + "group": "annoyances", + "parent": "AdGuard – Annoyances", + "off": true, + "title": "AdGuard – Other Annoyances", + "tags": "annoyances", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/21.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "adguard-widgets": { + "content": "filters", + "group": "annoyances", + "parent": "AdGuard – Annoyances", + "off": true, + "title": "AdGuard – Widgets", + "tags": "annoyances", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/22.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "fanboy-thirdparty_social": { + "content": "filters", + "group": "annoyances", + "off": true, + "title": "Fanboy – Anti-Facebook", + "tags": "privacy", + "contentURL": "https://secure.fanboy.co.nz/fanboy-antifacebook.txt", + "supportURL": "https://github.com/ryanbr/fanboy-adblock/issues" + }, + "easylist-annoyances": { + "content": "filters", + "group": "annoyances", + "parent": "EasyList – Annoyances", + "off": true, + "title": "EasyList – Other Annoyances", + "tags": "annoyances", + "contentURL": "https://ublockorigin.github.io/uAssets/thirdparties/easylist-annoyances.txt", + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-annoyances.txt", + "https://ublockorigin.pages.dev/thirdparties/easylist-annoyances.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-annoyances.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/thirdparties/easylist-annoyances.txt" + ], + "supportURL": "https://github.com/easylist/easylist#fanboy-lists" + }, + "easylist-chat": { + "content": "filters", + "group": "annoyances", + "parent": "EasyList – Annoyances", + "off": true, + "title": "EasyList – Chat Widgets", + "tags": "annoyances", + "contentURL": "https://ublockorigin.github.io/uAssets/thirdparties/easylist-chat.txt", + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-chat.txt", + "https://ublockorigin.pages.dev/thirdparties/easylist-chat.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-chat.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/thirdparties/easylist-chat.txt" + ], + "supportURL": "https://github.com/easylist/easylist#fanboy-lists" + }, + "fanboy-cookiemonster": { + "content": "filters", + "group": "annoyances", + "parent": "EasyList – Annoyances|EasyList/uBO – Cookie Notices", + "off": true, + "title": "EasyList – Cookie Notices", + "tags": "annoyances cookies", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/thirdparties/easylist-cookies.txt", + "https://secure.fanboy.co.nz/fanboy-cookiemonster_ubo.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-cookies.txt", + "https://ublockorigin.pages.dev/thirdparties/easylist-cookies.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-cookies.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/thirdparties/easylist-cookies.txt", + "https://secure.fanboy.co.nz/fanboy-cookiemonster_ubo.txt" + ], + "supportURL": "https://github.com/easylist/easylist#fanboy-lists" + }, + "ublock-cookies-easylist": { + "content": "filters", + "group": "annoyances", + "parent": "EasyList – Annoyances|EasyList/uBO – Cookie Notices", + "off": true, + "title": "uBlock filters – Cookie Notices", + "tags": "annoyances cookies", + "contentURL": "https://ublockorigin.github.io/uAssets/filters/annoyances-cookies.txt", + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/annoyances-cookies.txt", + "https://ublockorigin.pages.dev/filters/annoyances-cookies.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/annoyances-cookies.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/annoyances-cookies.txt" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets" + }, + "easylist-newsletters": { + "content": "filters", + "group": "annoyances", + "parent": "EasyList – Annoyances", + "off": true, + "title": "EasyList – Newsletter Notices", + "tags": "annoyances", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/thirdparties/easylist-newsletters.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-newsletters.txt", + "https://ublockorigin.pages.dev/thirdparties/easylist-newsletters.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-newsletters.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/thirdparties/easylist-newsletters.txt" + ], + "supportURL": "https://easylist.to/" + }, + "easylist-notifications": { + "content": "filters", + "group": "annoyances", + "parent": "EasyList – Annoyances", + "off": true, + "title": "EasyList – Notifications", + "tags": "annoyances", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/thirdparties/easylist-notifications.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-notifications.txt", + "https://ublockorigin.pages.dev/thirdparties/easylist-notifications.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-notifications.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/thirdparties/easylist-notifications.txt" + ], + "supportURL": "https://easylist.to/" + }, + "fanboy-social": { + "content": "filters", + "group": "annoyances", + "parent": "EasyList – Annoyances", + "off": true, + "title": "EasyList – Social Widgets", + "tags": "annoyances social", + "contentURL": [ + "https://ublockorigin.github.io/uAssets/thirdparties/easylist-social.txt", + "https://secure.fanboy.co.nz/fanboy-social_ubo.txt" + ], + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-social.txt", + "https://ublockorigin.pages.dev/thirdparties/easylist-social.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-social.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/thirdparties/easylist-social.txt", + "https://secure.fanboy.co.nz/fanboy-social_ubo.txt" + ], + "supportURL": "https://easylist.to/" + }, + "ublock-annoyances": { + "content": "filters", + "group": "annoyances", + "off": true, + "title": "uBlock filters – Annoyances", + "tags": "annoyances", + "contentURL": "https://ublockorigin.github.io/uAssets/filters/annoyances.txt", + "cdnURLs": [ + "https://ublockorigin.github.io/uAssetsCDN/filters/annoyances.min.txt", + "https://ublockorigin.pages.dev/filters/annoyances.min.txt", + "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/annoyances.min.txt", + "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/annoyances.min.txt" + ], + "supportURL": "https://github.com/uBlockOrigin/uAssets" + }, + "dpollock-0": { + "content": "filters", + "group": "multipurpose", + "updateAfter": 13, + "off": true, + "title": "Dan Pollock’s hosts file", + "tags": "ads privacy security", + "contentURL": "https://someonewhocares.org/hosts/hosts", + "supportURL": "https://someonewhocares.org/hosts/" + }, + "plowe-0": { + "content": "filters", + "group": "multipurpose", + "updateAfter": 13, + "title": "Peter Lowe’s Ad and tracking server list", + "tags": "ads privacy security", + "contentURL": [ + "https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=1&mimetype=plaintext", + "assets/thirdparties/pgl.yoyo.org/as/serverlist.txt", + "assets/thirdparties/pgl.yoyo.org/as/serverlist" + ], + "supportURL": "https://pgl.yoyo.org/adservers/" + }, + "ALB-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇦🇱al 🇽🇰xk: Adblock List for Albania", + "tags": "ads albania shqipja", + "lang": "sq", + "contentURL": "https://raw.githubusercontent.com/AnXh3L0/blocklist/master/albanian-easylist-addition/Albania.txt", + "supportURL": "https://github.com/AnXh3L0/blocklist" + }, + "ara-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇪🇬eg 🇸🇦sa 🇲🇦ma 🇩🇿dz: Liste AR", + "tags": "ads arabic اَلْعَرَبِيَّةُ", + "lang": "ar", + "contentURL": "https://easylist-downloads.adblockplus.org/Liste_AR.txt", + "supportURL": "https://forums.lanik.us/viewforum.php?f=98" + }, + "BGR-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇧🇬bg: Bulgarian Adblock list", + "tags": "ads bulgarian България macedonian Македонија", + "lang": "bg mk", + "contentURL": "https://stanev.org/abp/adblock_bg.txt", + "supportURL": "https://stanev.org/abp/" + }, + "CHN-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇨🇳cn 🇹🇼tw: AdGuard Chinese (中文)", + "tags": "ads chinese 中文", + "lang": "ug zh", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/224.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters" + }, + "CZE-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇨🇿cz 🇸🇰sk: EasyList Czech and Slovak", + "tags": "ads czech česká slovak slovenská", + "lang": "cs sk", + "contentURL": "https://raw.githubusercontent.com/tomasko126/easylistczechandslovak/master/filters.txt", + "supportURL": "https://github.com/tomasko126/easylistczechandslovak" + }, + "DEU-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇩🇪de 🇨🇭ch 🇦🇹at: EasyList Germany", + "tags": "ads german deutschland luxembourgish lëtzebuerg romansh", + "lang": "de dsb hsb lb rm", + "contentURL": [ + "https://easylist.to/easylistgermany/easylistgermany.txt", + "https://easylist-downloads.adblockplus.org/easylistgermany.txt" + ], + "supportURL": "https://forums.lanik.us/viewforum.php?f=90" + }, + "EST-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇪🇪ee: Eesti saitidele kohandatud filter", + "tags": "ads estonian", + "lang": "et", + "contentURL": "https://adblock.ee/list.php", + "supportURL": "https://adblock.ee/" + }, + "FIN-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇫🇮fi: Adblock List for Finland", + "tags": "ads finnish", + "lang": "fi", + "contentURL": "https://raw.githubusercontent.com/finnish-easylist-addition/finnish-easylist-addition/gh-pages/Finland_adb.txt", + "supportURL": "https://github.com/finnish-easylist-addition/finnish-easylist-addition" + }, + "FRA-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇫🇷fr 🇨🇦ca: AdGuard Français", + "tags": "ads french", + "lang": "ar br ff fr lb oc son", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/16.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters" + }, + "GRC-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇬🇷gr 🇨🇾cy: Greek AdBlock Filter", + "tags": "ads greek", + "lang": "el", + "contentURL": "https://www.void.gr/kargig/void-gr-filters.txt", + "supportURL": "https://github.com/kargig/greek-adblockplus-filter" + }, + "HRV-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇭🇷hr 🇷🇸rs: Dandelion Sprout's Serbo-Croatian filters", + "tags": "ads croatian serbian", + "lang": "hr sr", + "contentURL": "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/SerboCroatianList.txt", + "supportURL": "https://github.com/DandelionSprout/adfilt#readme" + }, + "HUN-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇭🇺hu: hufilter", + "tags": "ads hungarian", + "lang": "hu", + "contentURL": "https://raw.githubusercontent.com/hufilter/hufilter/master/hufilter-ublock.txt", + "supportURL": "https://github.com/hufilter/hufilter" + }, + "IDN-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇮🇩id 🇲🇾my: ABPindo", + "tags": "ads indonesian malay", + "lang": "id ms", + "contentURL": "https://raw.githubusercontent.com/ABPindo/indonesianadblockrules/master/subscriptions/abpindo.txt", + "supportURL": "https://github.com/ABPindo/indonesianadblockrules" + }, + "IND-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇮🇳in 🇱🇰lk 🇳🇵np: IndianList", + "tags": "ads assamese bengali gujarati hindi kannada malayalam marathi nepali punjabi sinhala tamil telugu", + "lang": "as bn gu hi kn ml mr ne pa si ta te", + "contentURL": "https://easylist-downloads.adblockplus.org/indianlist.txt", + "supportURL": "https://github.com/mediumkreation/IndianList" + }, + "IRN-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇮🇷ir: PersianBlocker", + "tags": "ads af ir persian pashto tajik tj", + "lang": "fa ps tg", + "contentURL": [ + "https://raw.githubusercontent.com/MasterKia/PersianBlocker/main/PersianBlocker.txt", + "https://cdn.statically.io/gh/MasterKia/PersianBlocker/main/PersianBlocker.txt", + "https://raw.githubusercontent.com/MasterKia/PersianBlocker/main/PersianBlocker.txt" + ], + "cdnURLs": [ + "https://cdn.jsdelivr.net/gh/MasterKia/PersianBlocker@main/PersianBlocker.txt", + "https://cdn.statically.io/gh/MasterKia/PersianBlocker/main/PersianBlocker.txt" + ], + "supportURL": "https://github.com/MasterKia/PersianBlocker" + }, + "ISL-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇮🇸is: Icelandic ABP List", + "tags": "ads icelandic", + "lang": "is", + "contentURL": "https://adblock.gardar.net/is.abp.txt", + "supportURL": "https://adblock.gardar.net/" + }, + "ISR-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇮🇱il: EasyList Hebrew", + "tags": "ads hebrew", + "lang": "he", + "contentURL": "https://raw.githubusercontent.com/easylist/EasyListHebrew/master/EasyListHebrew.txt", + "supportURL": "https://github.com/easylist/EasyListHebrew" + }, + "ITA-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇮🇹it: EasyList Italy", + "tags": "ads italian", + "lang": "it lij", + "contentURL": "https://easylist-downloads.adblockplus.org/easylistitaly.txt", + "supportURL": "https://forums.lanik.us/viewforum.php?f=96" + }, + "JPN-1": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇯🇵jp: AdGuard Japanese", + "tags": "ads japanese 日本語", + "lang": "ja", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/7.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "KOR-1": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇰🇷kr: List-KR", + "tags": "ads korean 한국어", + "lang": "ko", + "contentURL": "https://cdn.jsdelivr.net/gh/List-KR/List-KR@latest/filter-uBlockOrigin.txt", + "supportURL": "https://github.com/List-KR/List-KR#readme" + }, + "LTU-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇱🇹lt: EasyList Lithuania", + "tags": "ads lithuanian", + "lang": "lt", + "contentURL": "https://raw.githubusercontent.com/EasyList-Lithuania/easylist_lithuania/master/easylistlithuania.txt", + "cdnURLs": [ + "https://cdn.jsdelivr.net/gh/EasyList-Lithuania/easylist_lithuania@master/easylistlithuania.txt", + "https://cdn.statically.io/gh/EasyList-Lithuania/easylist_lithuania/master/easylistlithuania.txt" + ], + "supportURL": "https://github.com/EasyList-Lithuania/easylist_lithuania" + }, + "LVA-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇱🇻lv: Latvian List", + "tags": "ads latvian", + "lang": "lv", + "contentURL": "https://raw.githubusercontent.com/Latvian-List/adblock-latvian/master/lists/latvian-list.txt", + "supportURL": "https://github.com/Latvian-List/adblock-latvian" + }, + "MKD-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇲🇰mk: Macedonian adBlock Filters", + "tags": "ads macedonian", + "lang": "mk", + "contentURL": "https://raw.githubusercontent.com/DeepSpaceHarbor/Macedonian-adBlock-Filters/master/Filters", + "supportURL": "https://github.com/DeepSpaceHarbor/Macedonian-adBlock-Filters" + }, + "NLD-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇳🇱nl 🇧🇪be: EasyDutch", + "tags": "ads afrikaans be belgië frisian dutch flemish nederlands netherlands nl sr suriname za", + "lang": "af fy nl", + "contentURL": "https://easydutch-ubo.github.io/EasyDutch/EasyDutch.txt", + "cdnURLs": [ + "https://easydutch-ubo.github.io/EasyDutch/EasyDutch.all.txt", + "https://cdn.jsdelivr.net/gh/EasyDutch-uBO/EasyDutch@gh-pages/EasyDutch.all.txt", + "https://cdn.statically.io/gh/EasyDutch-uBO/EasyDutch/gh-pages/EasyDutch.all.txt" + ], + "supportURL": "https://github.com/EasyDutch-uBO/EasyDutch/" + }, + "NOR-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇳🇴no 🇩🇰dk 🇮🇸is: Dandelion Sprouts nordiske filtre", + "tags": "ads norwegian danish icelandic", + "lang": "nb nn no da is", + "contentURL": [ + "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/NorwegianList.txt" + ], + "cdnURLs": [ + "https://cdn.jsdelivr.net/gh/DandelionSprout/adfilt@master/NorwegianList.txt", + "https://cdn.statically.io/gl/DandelionSprout/adfilt/master/NorwegianList.txt" + ], + "supportURL": "https://github.com/DandelionSprout/adfilt" + }, + "POL-0": { + "content": "filters", + "group": "regions", + "parent": "🇵🇱pl: Oficjalne Polskie Filtry", + "off": true, + "title": "🇵🇱pl: Oficjalne Polskie Filtry do uBlocka Origin", + "tags": "ads polish polski", + "lang": "szl pl", + "contentURL": "https://raw.githubusercontent.com/MajkiIT/polish-ads-filter/master/polish-adblock-filters/adblock.txt", + "supportURL": "https://github.com/MajkiIT/polish-ads-filter/issues", + "instructionURL": "https://github.com/MajkiIT/polish-ads-filter#polish-filters-for-adblock-ublock-origin--adguard" + }, + "POL-2": { + "content": "filters", + "group": "regions", + "parent": "🇵🇱pl: Oficjalne Polskie Filtry", + "off": true, + "title": "🇵🇱pl: Oficjalne polskie filtry przeciwko alertom o Adblocku", + "tags": "ads polish polski", + "lang": "szl pl", + "contentURL": "https://raw.githubusercontent.com/olegwukr/polish-privacy-filters/master/anti-adblock.txt", + "supportURL": "https://github.com/olegwukr/polish-privacy-filters/issues" + }, + "ROU-1": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇷🇴ro 🇲🇩md: Romanian Ad (ROad) Block List Light", + "tags": "ads romanian română moldavian moldovenească молдовеняскэ", + "lang": "ro", + "contentURL": [ + "https://raw.githubusercontent.com/tcptomato/ROad-Block/master/road-block-filters-light.txt" + ], + "supportURL": "https://github.com/tcptomato/ROad-Block" + }, + "RUS-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇷🇺ru 🇺🇦ua 🇺🇿uz 🇰🇿kz: RU AdList", + "tags": "ads belarusian беларуская kazakh tatar russian русский ukrainian українська uzbek", + "lang": "be kk tt ru uk uz", + "contentURL": "https://raw.githubusercontent.com/easylist/ruadlist/master/RuAdList-uBO.txt", + "cdnURLs": [ + "https://cdn.jsdelivr.net/gh/dimisa-RUAdList/RUAdListCDN@main/lists/ruadlist.ubo.min.txt", + "https://cdn.statically.io/gh/dimisa-RUAdList/RUAdListCDN/main/lists/ruadlist.ubo.min.txt", + "https://raw.githubusercontent.com/dimisa-RUAdList/RUAdListCDN/main/lists/ruadlist.ubo.min.txt" + ], + "supportURL": "https://forums.lanik.us/viewforum.php?f=102", + "instructionURL": "https://forums.lanik.us/viewtopic.php?f=102&t=22512" + }, + "spa-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇪🇸es 🇦🇷ar 🇲🇽mx 🇨🇴co: EasyList Spanish", + "tags": "ads aragonese basque catalan spanish español galician guarani", + "lang": "an ast ca cak es eu gl gn trs quz", + "contentURL": "https://easylist-downloads.adblockplus.org/easylistspanish.txt", + "supportURL": "https://forums.lanik.us/viewforum.php?f=103" + }, + "spa-1": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇪🇸es 🇦🇷ar 🇧🇷br 🇵🇹pt: AdGuard Spanish/Portuguese", + "tags": "ads aragonese basque catalan spanish español galician guarani portuguese português", + "lang": "an ast ca cak es eu gl gn trs pt quz", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/9.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "SVN-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇸🇮si: Slovenian List", + "tags": "ads slovenian slovenski", + "lang": "sl", + "contentURL": "https://raw.githubusercontent.com/betterwebleon/slovenian-list/master/filters.txt", + "supportURL": "https://github.com/betterwebleon/slovenian-list" + }, + "SWE-1": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇸🇪se: Frellwit's Swedish Filter", + "tags": "ads swedish svenska", + "lang": "sv", + "contentURL": "https://raw.githubusercontent.com/lassekongo83/Frellwits-filter-lists/master/Frellwits-Swedish-Filter.txt", + "supportURL": "https://github.com/lassekongo83/Frellwits-filter-lists" + }, + "THA-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇹🇭th: EasyList Thailand", + "tags": "ads thai ไทย", + "lang": "th", + "contentURL": "https://raw.githubusercontent.com/easylist-thailand/easylist-thailand/master/subscription/easylist-thailand.txt", + "supportURL": "https://github.com/easylist-thailand/easylist-thailand" + }, + "TUR-0": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇹🇷tr: AdGuard Turkish", + "tags": "ads turkish türkçe", + "lang": "tr", + "contentURL": "https://filters.adtidy.org/extension/ublock/filters/13.txt", + "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", + "instructionURL": "https://kb.adguard.com/en/general/adguard-ad-filters" + }, + "VIE-1": { + "content": "filters", + "group": "regions", + "off": true, + "title": "🇻🇳vn: ABPVN List", + "tags": "ads vietnamese việt", + "lang": "vi", + "contentURL": "https://raw.githubusercontent.com/abpvn/abpvn/master/filter/abpvn_ublock.txt", + "supportURL": "https://abpvn.com/" + } +} diff --git a/assets/resources/scriptlets.js b/assets/resources/scriptlets.js new file mode 100644 index 0000000..6e2654f --- /dev/null +++ b/assets/resources/scriptlets.js @@ -0,0 +1,4376 @@ +/******************************************************************************* + + 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 + + The scriptlets below are meant to be injected only into a + web page context. +*/ + +// Externally added to the private namespace in which scriptlets execute. +/* global scriptletGlobals */ + +'use strict'; + +export const builtinScriptlets = []; + +/******************************************************************************* + + Helper functions + + These are meant to be used as dependencies to injectable scriptlets. + +*******************************************************************************/ + +builtinScriptlets.push({ + name: 'safe-self.fn', + fn: safeSelf, +}); +function safeSelf() { + if ( scriptletGlobals.has('safeSelf') ) { + return scriptletGlobals.get('safeSelf'); + } + const self = globalThis; + const safe = { + 'Array_from': Array.from, + 'Error': self.Error, + 'Function_toStringFn': self.Function.prototype.toString, + 'Function_toString': thisArg => safe.Function_toStringFn.call(thisArg), + 'Math_floor': Math.floor, + 'Math_max': Math.max, + 'Math_min': Math.min, + 'Math_random': Math.random, + 'Object_defineProperty': Object.defineProperty.bind(Object), + 'RegExp': self.RegExp, + 'RegExp_test': self.RegExp.prototype.test, + 'RegExp_exec': self.RegExp.prototype.exec, + 'Request_clone': self.Request.prototype.clone, + 'XMLHttpRequest': self.XMLHttpRequest, + 'addEventListener': self.EventTarget.prototype.addEventListener, + 'removeEventListener': self.EventTarget.prototype.removeEventListener, + 'fetch': self.fetch, + 'JSON': self.JSON, + 'JSON_parseFn': self.JSON.parse, + 'JSON_stringifyFn': self.JSON.stringify, + 'JSON_parse': (...args) => safe.JSON_parseFn.call(safe.JSON, ...args), + 'JSON_stringify': (...args) => safe.JSON_stringifyFn.call(safe.JSON, ...args), + 'log': console.log.bind(console), + uboLog(...args) { + if ( scriptletGlobals.has('canDebug') === false ) { return; } + if ( args.length === 0 ) { return; } + if ( `${args[0]}` === '' ) { return; } + this.log('[uBO]', ...args); + }, + initPattern(pattern, options = {}) { + if ( pattern === '' ) { + return { matchAll: true }; + } + const expect = (options.canNegate !== true || pattern.startsWith('!') === false); + if ( expect === false ) { + pattern = pattern.slice(1); + } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match !== null ) { + return { + re: new this.RegExp( + match[1], + match[2] || options.flags + ), + expect, + }; + } + if ( options.flags !== undefined ) { + return { + re: new this.RegExp(pattern.replace( + /[.*+?^${}()|[\]\\]/g, '\\$&'), + options.flags + ), + expect, + }; + } + return { pattern, expect }; + }, + testPattern(details, haystack) { + if ( details.matchAll ) { return true; } + if ( details.re ) { + return this.RegExp_test.call(details.re, haystack) === details.expect; + } + return haystack.includes(details.pattern) === details.expect; + }, + patternToRegex(pattern, flags = undefined, verbatim = false) { + if ( pattern === '' ) { return /^/; } + const match = /^\/(.+)\/([gimsu]*)$/.exec(pattern); + if ( match === null ) { + const reStr = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + return new RegExp(verbatim ? `^${reStr}$` : reStr, flags); + } + try { + return new RegExp(match[1], match[2] || undefined); + } + catch(ex) { + } + return /^/; + }, + getExtraArgs(args, offset = 0) { + const entries = args.slice(offset).reduce((out, v, i, a) => { + if ( (i & 1) === 0 ) { + const rawValue = a[i+1]; + const value = /^\d+$/.test(rawValue) + ? parseInt(rawValue, 10) + : rawValue; + out.push([ a[i], value ]); + } + return out; + }, []); + return Object.fromEntries(entries); + }, + }; + scriptletGlobals.set('safeSelf', safe); + return safe; +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'get-exception-token.fn', + fn: getExceptionToken, + dependencies: [ + 'safe-self.fn', + ], +}); +function getExceptionToken() { + const safe = safeSelf(); + const token = + String.fromCharCode(Date.now() % 26 + 97) + + safe.Math_floor(safe.Math_random() * 982451653 + 982451653).toString(36); + const oe = self.onerror; + self.onerror = function(msg, ...args) { + if ( typeof msg === 'string' && msg.includes(token) ) { return true; } + if ( oe instanceof Function ) { + return oe.call(this, msg, ...args); + } + }.bind(); + return token; +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'should-debug.fn', + fn: shouldDebug, +}); +function shouldDebug(details) { + if ( details instanceof Object === false ) { return false; } + return scriptletGlobals.has('canDebug') && details.debug; +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'should-log.fn', + fn: shouldLog, +}); +function shouldLog(details) { + if ( details instanceof Object === false ) { return false; } + return scriptletGlobals.has('canDebug') && details.log; +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'run-at.fn', + fn: runAt, + dependencies: [ + 'safe-self.fn', + ], +}); +function runAt(fn, when) { + const intFromReadyState = state => { + const targets = { + 'loading': 1, + 'interactive': 2, 'end': 2, '2': 2, + 'complete': 3, 'idle': 3, '3': 3, + }; + const tokens = Array.isArray(state) ? state : [ state ]; + for ( const token of tokens ) { + const prop = `${token}`; + if ( targets.hasOwnProperty(prop) === false ) { continue; } + return targets[prop]; + } + return 0; + }; + const runAt = intFromReadyState(when); + if ( intFromReadyState(document.readyState) >= runAt ) { + fn(); return; + } + const onStateChange = ( ) => { + if ( intFromReadyState(document.readyState) < runAt ) { return; } + fn(); + safe.removeEventListener.apply(document, args); + }; + const safe = safeSelf(); + const args = [ 'readystatechange', onStateChange, { capture: true } ]; + safe.addEventListener.apply(document, args); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'run-at-html-element.fn', + fn: runAtHtmlElementFn, +}); +function runAtHtmlElementFn(fn) { + if ( document.documentElement ) { + fn(); + return; + } + const observer = new MutationObserver(( ) => { + observer.disconnect(); + fn(); + }); + observer.observe(document, { childList: true }); +} + +/******************************************************************************/ + +// Reference: +// https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-scriptlets.md#prevent-xhr + +builtinScriptlets.push({ + name: 'generate-content.fn', + fn: generateContentFn, + dependencies: [ + 'safe-self.fn', + ], +}); +function generateContentFn(directive) { + const safe = safeSelf(); + const randomize = len => { + const chunks = []; + let textSize = 0; + do { + const s = safe.Math_random().toString(36).slice(2); + chunks.push(s); + textSize += s.length; + } + while ( textSize < len ); + return chunks.join(' ').slice(0, len); + }; + if ( directive === 'true' ) { + return Promise.resolve(randomize(10)); + } + if ( directive === 'emptyObj' ) { + return Promise.resolve('{}'); + } + if ( directive === 'emptyArr' ) { + return Promise.resolve('[]'); + } + if ( directive === 'emptyStr' ) { + return Promise.resolve(''); + } + if ( directive.startsWith('length:') ) { + const match = /^length:(\d+)(?:-(\d+))?$/.exec(directive); + if ( match ) { + const min = parseInt(match[1], 10); + const extent = safe.Math_max(parseInt(match[2], 10) || 0, min) - min; + const len = safe.Math_min(min + extent * safe.Math_random(), 500000); + return Promise.resolve(randomize(len | 0)); + } + } + if ( directive.startsWith('war:') && scriptletGlobals.has('warOrigin') ) { + return new Promise(resolve => { + const warOrigin = scriptletGlobals.get('warOrigin'); + const warName = directive.slice(4); + const fullpath = [ warOrigin, '/', warName ]; + const warSecret = scriptletGlobals.get('warSecret'); + if ( warSecret !== undefined ) { + fullpath.push('?secret=', warSecret); + } + const warXHR = new safe.XMLHttpRequest(); + warXHR.responseType = 'text'; + warXHR.onloadend = ev => { + resolve(ev.target.responseText || ''); + }; + warXHR.open('GET', fullpath.join('')); + warXHR.send(); + }); + } + return Promise.resolve(''); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'abort-current-script-core.fn', + fn: abortCurrentScriptCore, + dependencies: [ + 'get-exception-token.fn', + 'safe-self.fn', + 'should-debug.fn', + 'should-log.fn', + ], +}); +// Issues to mind before changing anything: +// https://github.com/uBlockOrigin/uBlock-issues/issues/2154 +function abortCurrentScriptCore( + target = '', + needle = '', + context = '' +) { + if ( typeof target !== 'string' ) { return; } + if ( target === '' ) { return; } + const safe = safeSelf(); + const reNeedle = safe.patternToRegex(needle); + const reContext = safe.patternToRegex(context); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); + const thisScript = document.currentScript; + const chain = target.split('.'); + let owner = window; + let prop; + for (;;) { + prop = chain.shift(); + if ( chain.length === 0 ) { break; } + if ( prop in owner === false ) { break; } + owner = owner[prop]; + if ( owner instanceof Object === false ) { return; } + } + let value; + let desc = Object.getOwnPropertyDescriptor(owner, prop); + if ( + desc instanceof Object === false || + desc.get instanceof Function === false + ) { + value = owner[prop]; + desc = undefined; + } + const log = shouldLog(extraArgs); + const debug = shouldDebug(extraArgs); + const exceptionToken = getExceptionToken(); + const scriptTexts = new WeakMap(); + const getScriptText = elem => { + let text = elem.textContent; + if ( text.trim() !== '' ) { return text; } + if ( scriptTexts.has(elem) ) { return scriptTexts.get(elem); } + const [ , mime, content ] = + /^data:([^,]*),(.+)$/.exec(elem.src.trim()) || + [ '', '', '' ]; + try { + switch ( true ) { + case mime.endsWith(';base64'): + text = self.atob(content); + break; + default: + text = self.decodeURIComponent(content); + break; + } + } catch(ex) { + } + scriptTexts.set(elem, text); + return text; + }; + const validate = ( ) => { + const e = document.currentScript; + if ( e instanceof HTMLScriptElement === false ) { return; } + if ( e === thisScript ) { return; } + if ( context !== '' && reContext.test(e.src) === false ) { + if ( debug === 'nomatch' || debug === 'all' ) { debugger; } // jshint ignore: line + return; + } + if ( log && e.src !== '' ) { safe.uboLog(`matched src: ${e.src}`); } + const scriptText = getScriptText(e); + if ( reNeedle.test(scriptText) === false ) { + if ( debug === 'nomatch' || debug === 'all' ) { debugger; } // jshint ignore: line + return; + } + if ( log ) { safe.uboLog(`matched script text: ${scriptText}`); } + if ( debug === 'match' || debug === 'all' ) { debugger; } // jshint ignore: line + throw new ReferenceError(exceptionToken); + }; + if ( debug === 'install' ) { debugger; } // jshint ignore: line + try { + Object.defineProperty(owner, prop, { + get: function() { + validate(); + return desc instanceof Object + ? desc.get.call(owner) + : value; + }, + set: function(a) { + validate(); + if ( desc instanceof Object ) { + desc.set.call(owner, a); + } else { + value = a; + } + } + }); + } catch(ex) { + if ( log ) { safe.uboLog(ex); } + } +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'set-constant-core.fn', + fn: setConstantCore, + dependencies: [ + 'run-at.fn', + 'safe-self.fn', + ], +}); + +function setConstantCore( + trusted = false, + chain = '', + cValue = '' +) { + if ( chain === '' ) { return; } + const safe = safeSelf(); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); + function setConstant(chain, cValue) { + const trappedProp = (( ) => { + const pos = chain.lastIndexOf('.'); + if ( pos === -1 ) { return chain; } + return chain.slice(pos+1); + })(); + if ( trappedProp === '' ) { return; } + const thisScript = document.currentScript; + const cloakFunc = fn => { + safe.Object_defineProperty(fn, 'name', { value: trappedProp }); + const proxy = new Proxy(fn, { + defineProperty(target, prop) { + if ( prop !== 'toString' ) { + return Reflect.defineProperty(...arguments); + } + return true; + }, + deleteProperty(target, prop) { + if ( prop !== 'toString' ) { + return Reflect.deleteProperty(...arguments); + } + return true; + }, + get(target, prop) { + if ( prop === 'toString' ) { + return function() { + return `function ${trappedProp}() { [native code] }`; + }.bind(null); + } + return Reflect.get(...arguments); + }, + }); + return proxy; + }; + if ( cValue === 'undefined' ) { + cValue = undefined; + } else if ( cValue === 'false' ) { + cValue = false; + } else if ( cValue === 'true' ) { + cValue = true; + } else if ( cValue === 'null' ) { + cValue = null; + } else if ( cValue === "''" || cValue === '' ) { + cValue = ''; + } else if ( cValue === '[]' || cValue === 'emptyArr' ) { + cValue = []; + } else if ( cValue === '{}' || cValue === 'emptyObj' ) { + cValue = {}; + } else if ( cValue === 'noopFunc' ) { + cValue = cloakFunc(function(){}); + } else if ( cValue === 'trueFunc' ) { + cValue = cloakFunc(function(){ return true; }); + } else if ( cValue === 'falseFunc' ) { + cValue = cloakFunc(function(){ return false; }); + } else if ( /^-?\d+$/.test(cValue) ) { + cValue = parseInt(cValue); + if ( isNaN(cValue) ) { return; } + if ( Math.abs(cValue) > 0x7FFF ) { return; } + } else if ( trusted ) { + if ( cValue.startsWith('{') && cValue.endsWith('}') ) { + try { cValue = safe.JSON_parse(cValue).value; } catch(ex) { return; } + } + } else { + return; + } + if ( extraArgs.as !== undefined ) { + const value = cValue; + if ( extraArgs.as === 'function' ) { + cValue = ( ) => value; + } else if ( extraArgs.as === 'callback' ) { + cValue = ( ) => (( ) => value); + } else if ( extraArgs.as === 'resolved' ) { + cValue = Promise.resolve(value); + } else if ( extraArgs.as === 'rejected' ) { + cValue = Promise.reject(value); + } + } + let aborted = false; + const mustAbort = function(v) { + if ( trusted ) { return false; } + if ( aborted ) { return true; } + aborted = + (v !== undefined && v !== null) && + (cValue !== undefined && cValue !== null) && + (typeof v !== typeof cValue); + return aborted; + }; + // https://github.com/uBlockOrigin/uBlock-issues/issues/156 + // Support multiple trappers for the same property. + const trapProp = function(owner, prop, configurable, handler) { + if ( handler.init(configurable ? owner[prop] : cValue) === false ) { return; } + const odesc = Object.getOwnPropertyDescriptor(owner, prop); + let prevGetter, prevSetter; + if ( odesc instanceof Object ) { + owner[prop] = cValue; + if ( odesc.get instanceof Function ) { + prevGetter = odesc.get; + } + if ( odesc.set instanceof Function ) { + prevSetter = odesc.set; + } + } + try { + safe.Object_defineProperty(owner, prop, { + configurable, + get() { + if ( prevGetter !== undefined ) { + prevGetter(); + } + return handler.getter(); // cValue + }, + set(a) { + if ( prevSetter !== undefined ) { + prevSetter(a); + } + handler.setter(a); + } + }); + } catch(ex) { + } + }; + const trapChain = function(owner, chain) { + const pos = chain.indexOf('.'); + if ( pos === -1 ) { + trapProp(owner, chain, false, { + v: undefined, + init: function(v) { + if ( mustAbort(v) ) { return false; } + this.v = v; + return true; + }, + getter: function() { + return document.currentScript === thisScript + ? this.v + : cValue; + }, + setter: function(a) { + if ( mustAbort(a) === false ) { return; } + cValue = a; + } + }); + return; + } + const prop = chain.slice(0, pos); + const v = owner[prop]; + chain = chain.slice(pos + 1); + if ( v instanceof Object || typeof v === 'object' && v !== null ) { + trapChain(v, chain); + return; + } + trapProp(owner, prop, true, { + v: undefined, + init: function(v) { + this.v = v; + return true; + }, + getter: function() { + return this.v; + }, + setter: function(a) { + this.v = a; + if ( a instanceof Object ) { + trapChain(a, chain); + } + } + }); + }; + trapChain(window, chain); + } + runAt(( ) => { + setConstant(chain, cValue); + }, extraArgs.runAt); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'replace-node-text.fn', + fn: replaceNodeTextFn, + dependencies: [ + 'run-at.fn', + 'safe-self.fn', + ], +}); +function replaceNodeTextFn( + nodeName = '', + pattern = '', + replacement = '' +) { + const safe = safeSelf(); + const reNodeName = safe.patternToRegex(nodeName, 'i', true); + const rePattern = safe.patternToRegex(pattern, 'gms'); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); + const shouldLog = scriptletGlobals.has('canDebug') && extraArgs.log || 0; + const reCondition = safe.patternToRegex(extraArgs.condition || '', 'ms'); + const stop = (takeRecord = true) => { + if ( takeRecord ) { + handleMutations(observer.takeRecords()); + } + observer.disconnect(); + if ( shouldLog !== 0 ) { + safe.uboLog(`replace-node-text-core.fn: quitting "${pattern}" => "${replacement}"`); + } + }; + let sedCount = extraArgs.sedCount || 0; + const handleNode = node => { + const before = node.textContent; + reCondition.lastIndex = 0; + if ( safe.RegExp_test.call(reCondition, before) === false ) { return true; } + rePattern.lastIndex = 0; + if ( safe.RegExp_test.call(rePattern, before) === false ) { return true; } + rePattern.lastIndex = 0; + const after = pattern !== '' + ? before.replace(rePattern, replacement) + : replacement; + node.textContent = after; + if ( shouldLog !== 0 ) { + safe.uboLog('replace-node-text.fn before:\n', before); + safe.uboLog('replace-node-text.fn after:\n', after); + } + return sedCount === 0 || (sedCount -= 1) !== 0; + }; + const handleMutations = mutations => { + for ( const mutation of mutations ) { + for ( const node of mutation.addedNodes ) { + if ( reNodeName.test(node.nodeName) === false ) { continue; } + if ( handleNode(node) ) { continue; } + stop(false); return; + } + } + }; + const observer = new MutationObserver(handleMutations); + observer.observe(document, { childList: true, subtree: true }); + if ( document.documentElement ) { + const treeWalker = document.createTreeWalker( + document.documentElement, + NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT + ); + let count = 0; + for (;;) { + const node = treeWalker.nextNode(); + count += 1; + if ( node === null ) { break; } + if ( reNodeName.test(node.nodeName) === false ) { continue; } + if ( handleNode(node) ) { continue; } + stop(); break; + } + if ( shouldLog !== 0 ) { + safe.uboLog(`replace-node-text-core.fn ${count} nodes present before installing mutation observer`); + } + } + if ( extraArgs.stay ) { return; } + runAt(( ) => { + const quitAfter = extraArgs.quitAfter || 0; + if ( quitAfter !== 0 ) { + setTimeout(( ) => { stop(); }, quitAfter); + } else { + stop(); + } + }, 'interactive'); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'object-prune.fn', + fn: objectPruneFn, + dependencies: [ + 'matches-stack-trace.fn', + 'object-find-owner.fn', + 'safe-self.fn', + 'should-log.fn', + ], +}); +// When no "prune paths" argument is provided, the scriptlet is +// used for logging purpose and the "needle paths" argument is +// used to filter logging output. +// +// https://github.com/uBlockOrigin/uBlock-issues/issues/1545 +// - Add support for "remove everything if needle matches" case +function objectPruneFn( + obj, + rawPrunePaths, + rawNeedlePaths, + stackNeedleDetails = { matchAll: true }, + extraArgs = {} +) { + if ( typeof rawPrunePaths !== 'string' ) { return; } + const safe = safeSelf(); + const prunePaths = rawPrunePaths !== '' + ? rawPrunePaths.split(/ +/) + : []; + const needlePaths = prunePaths.length !== 0 && rawNeedlePaths !== '' + ? rawNeedlePaths.split(/ +/) + : []; + const logLevel = shouldLog({ log: rawPrunePaths === '' || extraArgs.log }); + const reLogNeedle = safe.patternToRegex(logLevel === true ? rawNeedlePaths : ''); + if ( stackNeedleDetails.matchAll !== true ) { + if ( matchesStackTrace(stackNeedleDetails, extraArgs.logstack) === false ) { + return; + } + } + if ( objectPruneFn.mustProcess === undefined ) { + objectPruneFn.mustProcess = (root, needlePaths) => { + for ( const needlePath of needlePaths ) { + if ( objectFindOwnerFn(root, needlePath) === false ) { + return false; + } + } + return true; + }; + objectPruneFn.logJson = (json, msg, reNeedle) => { + if ( reNeedle.test(json) === false ) { return; } + safeSelf().uboLog(`objectPrune()`, msg, location.hostname, json); + }; + } + const jsonBefore = logLevel ? safe.JSON_stringify(obj, null, 2) : ''; + if ( logLevel === true || logLevel === 'all' ) { + objectPruneFn.logJson(jsonBefore, `prune:"${rawPrunePaths}" log:"${logLevel}"`, reLogNeedle); + } + if ( prunePaths.length === 0 ) { return; } + let outcome = 'nomatch'; + if ( objectPruneFn.mustProcess(obj, needlePaths) ) { + for ( const path of prunePaths ) { + if ( objectFindOwnerFn(obj, path, true) ) { + outcome = 'match'; + } + } + } + if ( logLevel === outcome ) { + objectPruneFn.logJson(jsonBefore, `prune:"${rawPrunePaths}" log:"${logLevel}"`, reLogNeedle); + } + if ( outcome === 'match' ) { return obj; } +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'object-find-owner.fn', + fn: objectFindOwnerFn, +}); +function objectFindOwnerFn( + root, + path, + prune = false +) { + let owner = root; + let chain = path; + for (;;) { + if ( typeof owner !== 'object' || owner === null ) { return false; } + const pos = chain.indexOf('.'); + if ( pos === -1 ) { + if ( prune === false ) { + return owner.hasOwnProperty(chain); + } + let modified = false; + if ( chain === '*' ) { + for ( const key in owner ) { + if ( owner.hasOwnProperty(key) === false ) { continue; } + delete owner[key]; + modified = true; + } + } else if ( owner.hasOwnProperty(chain) ) { + delete owner[chain]; + modified = true; + } + return modified; + } + const prop = chain.slice(0, pos); + if ( + prop === '[]' && Array.isArray(owner) || + prop === '*' && owner instanceof Object + ) { + const next = chain.slice(pos + 1); + let found = false; + for ( const key of Object.keys(owner) ) { + found = objectFindOwnerFn(owner[key], next, prune) || found; + } + return found; + } + if ( owner.hasOwnProperty(prop) === false ) { return false; } + owner = owner[prop]; + chain = chain.slice(pos + 1); + } + return true; +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'set-cookie.fn', + fn: setCookieFn, +}); +function setCookieFn( + trusted = false, + name = '', + value = '', + expires = '', + path = '', + options = {}, +) { + const getCookieValue = name => { + for ( const s of document.cookie.split(/\s*;\s*/) ) { + const pos = s.indexOf('='); + if ( pos === -1 ) { continue; } + if ( s.slice(0, pos) !== name ) { continue; } + return s.slice(pos+1); + } + }; + + const cookieBefore = getCookieValue(name); + if ( cookieBefore !== undefined && options.dontOverwrite ) { return; } + if ( cookieBefore === value && options.reload ) { return; } + + const cookieParts = [ name, '=', value ]; + if ( expires !== '' ) { + cookieParts.push('; expires=', expires); + } + + if ( path === '' ) { path = '/'; } + else if ( path === 'none' ) { path = ''; } + if ( path !== '' && path !== '/' ) { return; } + if ( path === '/' ) { + cookieParts.push('; path=/'); + } + + if ( trusted ) { + if ( options.domain ) { + cookieParts.push(`; domain=${options.domain}`); + } + cookieParts.push('; Secure'); + } + + try { + document.cookie = cookieParts.join(''); + } catch(_) { + } + + if ( options.reload && getCookieValue(name) === value ) { + window.location.reload(); + } +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'set-local-storage-item.fn', + fn: setLocalStorageItemFn, + dependencies: [ + 'safe-self.fn', + ], +}); +function setLocalStorageItemFn( + which = 'local', + trusted = false, + key = '', + value = '', +) { + if ( key === '' ) { return; } + + // For increased compatibility with AdGuard + if ( value === 'emptyArr' ) { + value = '[]'; + } else if ( value === 'emptyObj' ) { + value = '{}'; + } + + const trustedValues = [ + '', + 'undefined', 'null', + 'false', 'true', + 'on', 'off', + 'yes', 'no', + '{}', '[]', '""', + '$remove$', + ]; + + if ( trusted ) { + if ( value === '$now$' ) { + value = Date.now(); + } else if ( value === '$currentDate$' ) { + value = `${Date()}`; + } else if ( value === '$currentISODate$' ) { + value = (new Date()).toISOString(); + } + } else { + const normalized = value.toLowerCase(); + const match = /^("?)(.+)\1$/.exec(normalized); + const unquoted = match && match[2] || normalized; + if ( trustedValues.includes(unquoted) === false ) { + if ( /^\d+$/.test(unquoted) === false ) { return; } + const n = parseInt(unquoted, 10); + if ( n > 32767 ) { return; } + } + } + + try { + const storage = self[`${which}Storage`]; + if ( value === '$remove$' ) { + const safe = safeSelf(); + const pattern = safe.patternToRegex(key, undefined, true ); + const toRemove = []; + for ( let i = 0, n = storage.length; i < n; i++ ) { + const key = storage.key(i); + if ( pattern.test(key) ) { toRemove.push(key); } + } + for ( const key of toRemove ) { + storage.removeItem(key); + } + } else { + storage.setItem(key, `${value}`); + } + } catch(ex) { + } +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'matches-stack-trace.fn', + fn: matchesStackTrace, + dependencies: [ + 'get-exception-token.fn', + 'safe-self.fn', + ], +}); +function matchesStackTrace( + needleDetails, + logLevel = '' +) { + const safe = safeSelf(); + const exceptionToken = getExceptionToken(); + const error = new safe.Error(exceptionToken); + const docURL = new URL(self.location.href); + docURL.hash = ''; + // Normalize stack trace + const reLine = /(.*?@)?(\S+)(:\d+):\d+\)?$/; + const lines = []; + for ( let line of error.stack.split(/[\n\r]+/) ) { + if ( line.includes(exceptionToken) ) { continue; } + line = line.trim(); + const match = safe.RegExp_exec.call(reLine, line); + if ( match === null ) { continue; } + let url = match[2]; + if ( url.startsWith('(') ) { url = url.slice(1); } + if ( url === docURL.href ) { + url = 'inlineScript'; + } else if ( url.startsWith('<anonymous>') ) { + url = 'injectedScript'; + } + let fn = match[1] !== undefined + ? match[1].slice(0, -1) + : line.slice(0, match.index).trim(); + if ( fn.startsWith('at') ) { fn = fn.slice(2).trim(); } + let rowcol = match[3]; + lines.push(' ' + `${fn} ${url}${rowcol}:1`.trim()); + } + lines[0] = `stackDepth:${lines.length-1}`; + const stack = lines.join('\t'); + const r = needleDetails.matchAll !== true && + safe.testPattern(needleDetails, stack); + if ( + logLevel === 'all' || + logLevel === 'match' && r || + logLevel === 'nomatch' && !r + ) { + safe.uboLog(stack.replace(/\t/g, '\n')); + } + return r; +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'parse-properties-to-match.fn', + fn: parsePropertiesToMatch, + dependencies: [ + 'safe-self.fn', + ], +}); +function parsePropertiesToMatch(propsToMatch, implicit = '') { + const safe = safeSelf(); + const needles = new Map(); + if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; } + const options = { canNegate: true }; + for ( const needle of propsToMatch.split(/\s+/) ) { + const [ prop, pattern ] = needle.split(':'); + if ( prop === '' ) { continue; } + if ( pattern !== undefined ) { + needles.set(prop, safe.initPattern(pattern, options)); + } else if ( implicit !== '' ) { + needles.set(implicit, safe.initPattern(prop, options)); + } + } + return needles; +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'match-object-properties.fn', + fn: matchObjectProperties, + dependencies: [ + 'safe-self.fn', + ], +}); +function matchObjectProperties(propNeedles, ...objs) { + if ( matchObjectProperties.extractProperties === undefined ) { + matchObjectProperties.extractProperties = (src, des, props) => { + for ( const p of props ) { + const v = src[p]; + if ( v === undefined ) { continue; } + des[p] = src[p]; + } + }; + } + const safe = safeSelf(); + const haystack = {}; + const props = safe.Array_from(propNeedles.keys()); + for ( const obj of objs ) { + if ( obj instanceof Object === false ) { continue; } + matchObjectProperties.extractProperties(obj, haystack, props); + } + for ( const [ prop, details ] of propNeedles ) { + let value = haystack[prop]; + if ( value === undefined ) { continue; } + if ( typeof value !== 'string' ) { + try { value = JSON.stringify(value); } + catch(ex) { } + if ( typeof value !== 'string' ) { continue; } + } + if ( safe.testPattern(details, value) ) { continue; } + return false; + } + return true; +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'json-prune-fetch-response.fn', + fn: jsonPruneFetchResponseFn, + dependencies: [ + 'match-object-properties.fn', + 'object-prune.fn', + 'parse-properties-to-match.fn', + 'safe-self.fn', + 'should-log.fn', + ], +}); +function jsonPruneFetchResponseFn( + rawPrunePaths = '', + rawNeedlePaths = '' +) { + const safe = safeSelf(); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); + const logLevel = shouldLog({ log: rawPrunePaths === '' || extraArgs.log, }); + const log = logLevel ? ((...args) => { safe.uboLog(...args); }) : (( ) => { }); + const propNeedles = parsePropertiesToMatch(extraArgs.propsToMatch, 'url'); + const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true }); + const applyHandler = function(target, thisArg, args) { + const fetchPromise = Reflect.apply(target, thisArg, args); + if ( logLevel === true ) { + log('json-prune-fetch-response:', JSON.stringify(Array.from(args)).slice(1,-1)); + } + if ( rawPrunePaths === '' ) { return fetchPromise; } + let outcome = 'match'; + if ( propNeedles.size !== 0 ) { + const objs = [ args[0] instanceof Object ? args[0] : { url: args[0] } ]; + if ( objs[0] instanceof Request ) { + try { objs[0] = safe.Request_clone.call(objs[0]); } + catch(ex) { log(ex); } + } + if ( args[1] instanceof Object ) { + objs.push(args[1]); + } + if ( matchObjectProperties(propNeedles, ...objs) === false ) { + outcome = 'nomatch'; + } + if ( outcome === logLevel || logLevel === 'all' ) { + log( + `json-prune-fetch-response (${outcome})`, + `\n\tfetchPropsToMatch: ${JSON.stringify(Array.from(propNeedles)).slice(1,-1)}`, + '\n\tprops:', ...objs, + ); + } + } + if ( outcome === 'nomatch' ) { return fetchPromise; } + return fetchPromise.then(responseBefore => { + const response = responseBefore.clone(); + return response.json().then(objBefore => { + if ( typeof objBefore !== 'object' ) { return responseBefore; } + const objAfter = objectPruneFn( + objBefore, + rawPrunePaths, + rawNeedlePaths, + stackNeedle, + extraArgs + ); + if ( typeof objAfter !== 'object' ) { return responseBefore; } + const responseAfter = Response.json(objAfter, { + status: responseBefore.status, + statusText: responseBefore.statusText, + headers: responseBefore.headers, + }); + Object.defineProperties(responseAfter, { + ok: { value: responseBefore.ok }, + redirected: { value: responseBefore.redirected }, + type: { value: responseBefore.type }, + url: { value: responseBefore.url }, + }); + return responseAfter; + }).catch(reason => { + log('json-prune-fetch-response:', reason); + return responseBefore; + }); + }).catch(reason => { + log('json-prune-fetch-response:', reason); + return fetchPromise; + }); + }; + self.fetch = new Proxy(self.fetch, { + apply: applyHandler + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'replace-fetch-response.fn', + fn: replaceFetchResponseFn, + dependencies: [ + 'match-object-properties.fn', + 'parse-properties-to-match.fn', + 'safe-self.fn', + 'should-log.fn', + ], +}); +function replaceFetchResponseFn( + trusted = false, + pattern = '', + replacement = '', + propsToMatch = '' +) { + if ( trusted !== true ) { return; } + const safe = safeSelf(); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 4); + const logLevel = shouldLog({ + log: pattern === '' || extraArgs.log, + }); + const log = logLevel ? ((...args) => { safe.uboLog(...args); }) : (( ) => { }); + if ( pattern === '*' ) { pattern = '.*'; } + const rePattern = safe.patternToRegex(pattern); + const propNeedles = parsePropertiesToMatch(propsToMatch, 'url'); + self.fetch = new Proxy(self.fetch, { + apply: function(target, thisArg, args) { + if ( logLevel === true ) { + log('replace-fetch-response:', JSON.stringify(Array.from(args)).slice(1,-1)); + } + const fetchPromise = Reflect.apply(target, thisArg, args); + if ( pattern === '' ) { return fetchPromise; } + let outcome = 'match'; + if ( propNeedles.size !== 0 ) { + const objs = [ args[0] instanceof Object ? args[0] : { url: args[0] } ]; + if ( objs[0] instanceof Request ) { + try { objs[0] = safe.Request_clone.call(objs[0]); } + catch(ex) { log(ex); } + } + if ( args[1] instanceof Object ) { + objs.push(args[1]); + } + if ( matchObjectProperties(propNeedles, ...objs) === false ) { + outcome = 'nomatch'; + } + if ( outcome === logLevel || logLevel === 'all' ) { + log( + `replace-fetch-response (${outcome})`, + `\n\tpropsToMatch: ${JSON.stringify(Array.from(propNeedles)).slice(1,-1)}`, + '\n\tprops:', ...args, + ); + } + } + if ( outcome === 'nomatch' ) { return fetchPromise; } + return fetchPromise.then(responseBefore => { + const response = responseBefore.clone(); + return response.text().then(textBefore => { + const textAfter = textBefore.replace(rePattern, replacement); + const outcome = textAfter !== textBefore ? 'match' : 'nomatch'; + if ( outcome === logLevel || logLevel === 'all' ) { + log( + `replace-fetch-response (${outcome})`, + `\n\tpattern: ${pattern}`, + `\n\treplacement: ${replacement}`, + ); + } + if ( outcome === 'nomatch' ) { return responseBefore; } + const responseAfter = new Response(textAfter, { + status: responseBefore.status, + statusText: responseBefore.statusText, + headers: responseBefore.headers, + }); + Object.defineProperties(responseAfter, { + ok: { value: responseBefore.ok }, + redirected: { value: responseBefore.redirected }, + type: { value: responseBefore.type }, + url: { value: responseBefore.url }, + }); + return responseAfter; + }).catch(reason => { + log('replace-fetch-response:', reason); + return responseBefore; + }); + }).catch(reason => { + log('replace-fetch-response:', reason); + return fetchPromise; + }); + } + }); +} + + +/******************************************************************************* + + Injectable scriptlets + + These are meant to be used in the MAIN (webpage) execution world. + +*******************************************************************************/ + +builtinScriptlets.push({ + name: 'abort-current-script.js', + aliases: [ + 'acs.js', + 'abort-current-inline-script.js', + 'acis.js', + ], + fn: abortCurrentScript, + dependencies: [ + 'abort-current-script-core.fn', + 'run-at-html-element.fn', + ], +}); +// Issues to mind before changing anything: +// https://github.com/uBlockOrigin/uBlock-issues/issues/2154 +function abortCurrentScript(...args) { + runAtHtmlElementFn(( ) => { + abortCurrentScriptCore(...args); + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'abort-on-property-read.js', + aliases: [ + 'aopr.js', + ], + fn: abortOnPropertyRead, + dependencies: [ + 'get-exception-token.fn', + ], +}); +function abortOnPropertyRead( + chain = '' +) { + if ( typeof chain !== 'string' ) { return; } + if ( chain === '' ) { return; } + const exceptionToken = getExceptionToken(); + const abort = function() { + throw new ReferenceError(exceptionToken); + }; + const makeProxy = function(owner, chain) { + const pos = chain.indexOf('.'); + if ( pos === -1 ) { + const desc = Object.getOwnPropertyDescriptor(owner, chain); + if ( !desc || desc.get !== abort ) { + Object.defineProperty(owner, chain, { + get: abort, + set: function(){} + }); + } + return; + } + const prop = chain.slice(0, pos); + let v = owner[prop]; + chain = chain.slice(pos + 1); + if ( v ) { + makeProxy(v, chain); + return; + } + const desc = Object.getOwnPropertyDescriptor(owner, prop); + if ( desc && desc.set !== undefined ) { return; } + Object.defineProperty(owner, prop, { + get: function() { return v; }, + set: function(a) { + v = a; + if ( a instanceof Object ) { + makeProxy(a, chain); + } + } + }); + }; + const owner = window; + makeProxy(owner, chain); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'abort-on-property-write.js', + aliases: [ + 'aopw.js', + ], + fn: abortOnPropertyWrite, + dependencies: [ + 'get-exception-token.fn', + ], +}); +function abortOnPropertyWrite( + prop = '' +) { + if ( typeof prop !== 'string' ) { return; } + if ( prop === '' ) { return; } + const exceptionToken = getExceptionToken(); + let owner = window; + for (;;) { + const pos = prop.indexOf('.'); + if ( pos === -1 ) { break; } + owner = owner[prop.slice(0, pos)]; + if ( owner instanceof Object === false ) { return; } + prop = prop.slice(pos + 1); + } + delete owner[prop]; + Object.defineProperty(owner, prop, { + set: function() { + throw new ReferenceError(exceptionToken); + } + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'abort-on-stack-trace.js', + aliases: [ + 'aost.js', + ], + fn: abortOnStackTrace, + dependencies: [ + 'get-exception-token.fn', + 'matches-stack-trace.fn', + 'safe-self.fn', + ], +}); +function abortOnStackTrace( + chain = '', + needle = '' +) { + if ( typeof chain !== 'string' ) { return; } + const safe = safeSelf(); + const needleDetails = safe.initPattern(needle, { canNegate: true }); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); + if ( needle === '' ) { extraArgs.log = 'all'; } + const makeProxy = function(owner, chain) { + const pos = chain.indexOf('.'); + if ( pos === -1 ) { + let v = owner[chain]; + Object.defineProperty(owner, chain, { + get: function() { + if ( matchesStackTrace(needleDetails, extraArgs.log) ) { + throw new ReferenceError(getExceptionToken()); + } + return v; + }, + set: function(a) { + if ( matchesStackTrace(needleDetails, extraArgs.log) ) { + throw new ReferenceError(getExceptionToken()); + } + v = a; + }, + }); + return; + } + const prop = chain.slice(0, pos); + let v = owner[prop]; + chain = chain.slice(pos + 1); + if ( v ) { + makeProxy(v, chain); + return; + } + const desc = Object.getOwnPropertyDescriptor(owner, prop); + if ( desc && desc.set !== undefined ) { return; } + Object.defineProperty(owner, prop, { + get: function() { return v; }, + set: function(a) { + v = a; + if ( a instanceof Object ) { + makeProxy(a, chain); + } + } + }); + }; + const owner = window; + makeProxy(owner, chain); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'addEventListener-defuser.js', + aliases: [ + 'aeld.js', + 'prevent-addEventListener.js', + ], + fn: addEventListenerDefuser, + dependencies: [ + 'run-at.fn', + 'safe-self.fn', + 'should-debug.fn', + 'should-log.fn', + ], +}); +// https://github.com/uBlockOrigin/uAssets/issues/9123#issuecomment-848255120 +function addEventListenerDefuser( + type = '', + pattern = '' +) { + const safe = safeSelf(); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); + const reType = safe.patternToRegex(type, undefined, true); + const rePattern = safe.patternToRegex(pattern); + const log = shouldLog(extraArgs); + const debug = shouldDebug(extraArgs); + const trapEddEventListeners = ( ) => { + const eventListenerHandler = { + apply: function(target, thisArg, args) { + let type, handler; + try { + type = String(args[0]); + handler = args[1] instanceof Function + ? String(safe.Function_toString(args[1])) + : String(args[1]); + } catch(ex) { + } + const matchesType = safe.RegExp_test.call(reType, type); + const matchesHandler = safe.RegExp_test.call(rePattern, handler); + const matchesEither = matchesType || matchesHandler; + const matchesBoth = matchesType && matchesHandler; + if ( log === 1 && matchesBoth || log === 2 && matchesEither || log === 3 ) { + safe.uboLog(`addEventListener('${type}', ${handler})`); + } + if ( debug === 1 && matchesBoth || debug === 2 && matchesEither ) { + debugger; // jshint ignore:line + } + if ( matchesBoth ) { return; } + return Reflect.apply(target, thisArg, args); + }, + get(target, prop, receiver) { + if ( prop === 'toString' ) { + return target.toString.bind(target); + } + return Reflect.get(target, prop, receiver); + }, + }; + self.EventTarget.prototype.addEventListener = new Proxy( + self.EventTarget.prototype.addEventListener, + eventListenerHandler + ); + }; + runAt(( ) => { + trapEddEventListeners(); + }, extraArgs.runAt); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'json-prune.js', + fn: jsonPrune, + dependencies: [ + 'object-prune.fn', + 'safe-self.fn', + ], +}); +function jsonPrune( + rawPrunePaths = '', + rawNeedlePaths = '', + stackNeedle = '' +) { + const safe = safeSelf(); + const stackNeedleDetails = safe.initPattern(stackNeedle, { canNegate: true }); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); + JSON.parse = new Proxy(JSON.parse, { + apply: function(target, thisArg, args) { + const objBefore = Reflect.apply(target, thisArg, args); + const objAfter = objectPruneFn( + objBefore, + rawPrunePaths, + rawNeedlePaths, + stackNeedleDetails, + extraArgs + ); + return objAfter || objBefore; + }, + }); +} + +/******************************************************************************* + * + * json-prune-fetch-response.js + * + * Prune JSON response of fetch requests. + * + **/ + +builtinScriptlets.push({ + name: 'json-prune-fetch-response.js', + fn: jsonPruneFetchResponse, + dependencies: [ + 'json-prune-fetch-response.fn', + ], +}); +function jsonPruneFetchResponse(...args) { + jsonPruneFetchResponseFn(...args); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'json-prune-xhr-response.js', + fn: jsonPruneXhrResponse, + dependencies: [ + 'match-object-properties.fn', + 'object-prune.fn', + 'parse-properties-to-match.fn', + 'safe-self.fn', + 'should-log.fn', + ], +}); +function jsonPruneXhrResponse( + rawPrunePaths = '', + rawNeedlePaths = '' +) { + const safe = safeSelf(); + const xhrInstances = new WeakMap(); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); + const logLevel = shouldLog({ log: rawPrunePaths === '' || extraArgs.log, }); + const log = logLevel ? ((...args) => { safe.uboLog(...args); }) : (( ) => { }); + const propNeedles = parsePropertiesToMatch(extraArgs.propsToMatch, 'url'); + const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true }); + self.XMLHttpRequest = class extends self.XMLHttpRequest { + open(method, url, ...args) { + const xhrDetails = { method, url }; + let outcome = 'match'; + if ( propNeedles.size !== 0 ) { + if ( matchObjectProperties(propNeedles, xhrDetails) === false ) { + outcome = 'nomatch'; + } + } + if ( outcome === logLevel || outcome === 'all' ) { + log(`xhr.open(${method}, ${url}, ${args.join(', ')})`); + } + if ( outcome === 'match' ) { + xhrInstances.set(this, xhrDetails); + } + return super.open(method, url, ...args); + } + get response() { + const innerResponse = super.response; + const xhrDetails = xhrInstances.get(this); + if ( xhrDetails === undefined ) { + return innerResponse; + } + const responseLength = typeof innerResponse === 'string' + ? innerResponse.length + : undefined; + if ( xhrDetails.lastResponseLength !== responseLength ) { + xhrDetails.response = undefined; + xhrDetails.lastResponseLength = responseLength; + } + if ( xhrDetails.response !== undefined ) { + return xhrDetails.response; + } + let objBefore; + if ( typeof innerResponse === 'object' ) { + objBefore = innerResponse; + } else if ( typeof innerResponse === 'string' ) { + try { objBefore = safe.JSON_parse(innerResponse); } + catch(ex) { } + } + if ( typeof objBefore !== 'object' ) { + return (xhrDetails.response = innerResponse); + } + const objAfter = objectPruneFn( + objBefore, + rawPrunePaths, + rawNeedlePaths, + stackNeedle, + extraArgs + ); + let outerResponse; + if ( typeof objAfter === 'object' ) { + outerResponse = typeof innerResponse === 'string' + ? safe.JSON_stringify(objAfter) + : objAfter; + } else { + outerResponse = innerResponse; + } + return (xhrDetails.response = outerResponse); + } + get responseText() { + const response = this.response; + return typeof response !== 'string' + ? super.responseText + : response; + } + }; +} + +/******************************************************************************/ + +// There is still code out there which uses `eval` in lieu of `JSON.parse`. + +builtinScriptlets.push({ + name: 'evaldata-prune.js', + fn: evaldataPrune, + dependencies: [ + 'object-prune.fn', + ], +}); +function evaldataPrune( + rawPrunePaths = '', + rawNeedlePaths = '' +) { + self.eval = new Proxy(self.eval, { + apply(target, thisArg, args) { + const before = Reflect.apply(target, thisArg, args); + if ( typeof before === 'object' ) { + const after = objectPruneFn(before, rawPrunePaths, rawNeedlePaths); + return after || before; + } + return before; + } + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'adjust-setInterval.js', + aliases: [ + 'nano-setInterval-booster.js', + 'nano-sib.js', + ], + fn: adjustSetInterval, + dependencies: [ + 'safe-self.fn', + ], +}); +// Imported from: +// https://github.com/NanoAdblocker/NanoFilters/blob/1f3be7211bb0809c5106996f52564bf10c4525f7/NanoFiltersSource/NanoResources.txt#L126 +// +// Speed up or down setInterval, 3 optional arguments. +// The payload matcher, a string literal or a JavaScript RegExp, defaults +// to match all. +// delayMatcher +// The delay matcher, an integer, defaults to 1000. +// Use `*` to match any delay. +// boostRatio - The delay multiplier when there is a match, 0.5 speeds up by +// 2 times and 2 slows down by 2 times, defaults to 0.05 or speed up +// 20 times. Speed up and down both cap at 50 times. +function adjustSetInterval( + needleArg = '', + delayArg = '', + boostArg = '' +) { + if ( typeof needleArg !== 'string' ) { return; } + const safe = safeSelf(); + const reNeedle = safe.patternToRegex(needleArg); + let delay = delayArg !== '*' ? parseInt(delayArg, 10) : -1; + if ( isNaN(delay) || isFinite(delay) === false ) { delay = 1000; } + let boost = parseFloat(boostArg); + boost = isNaN(boost) === false && isFinite(boost) + ? Math.min(Math.max(boost, 0.001), 50) + : 0.05; + self.setInterval = new Proxy(self.setInterval, { + apply: function(target, thisArg, args) { + const [ a, b ] = args; + if ( + (delay === -1 || b === delay) && + reNeedle.test(a.toString()) + ) { + args[1] = b * boost; + } + return target.apply(thisArg, args); + } + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'adjust-setTimeout.js', + aliases: [ + 'nano-setTimeout-booster.js', + 'nano-stb.js', + ], + fn: adjustSetTimeout, + dependencies: [ + 'safe-self.fn', + ], +}); +// Imported from: +// https://github.com/NanoAdblocker/NanoFilters/blob/1f3be7211bb0809c5106996f52564bf10c4525f7/NanoFiltersSource/NanoResources.txt#L82 +// +// Speed up or down setTimeout, 3 optional arguments. +// funcMatcher +// The payload matcher, a string literal or a JavaScript RegExp, defaults +// to match all. +// delayMatcher +// The delay matcher, an integer, defaults to 1000. +// Use `*` to match any delay. +// boostRatio - The delay multiplier when there is a match, 0.5 speeds up by +// 2 times and 2 slows down by 2 times, defaults to 0.05 or speed up +// 20 times. Speed up and down both cap at 50 times. +function adjustSetTimeout( + needleArg = '', + delayArg = '', + boostArg = '' +) { + if ( typeof needleArg !== 'string' ) { return; } + const safe = safeSelf(); + const reNeedle = safe.patternToRegex(needleArg); + let delay = delayArg !== '*' ? parseInt(delayArg, 10) : -1; + if ( isNaN(delay) || isFinite(delay) === false ) { delay = 1000; } + let boost = parseFloat(boostArg); + boost = isNaN(boost) === false && isFinite(boost) + ? Math.min(Math.max(boost, 0.001), 50) + : 0.05; + self.setTimeout = new Proxy(self.setTimeout, { + apply: function(target, thisArg, args) { + const [ a, b ] = args; + if ( + (delay === -1 || b === delay) && + reNeedle.test(a.toString()) + ) { + args[1] = b * boost; + } + return target.apply(thisArg, args); + } + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'noeval-if.js', + aliases: [ + 'prevent-eval-if.js', + ], + fn: noEvalIf, + dependencies: [ + 'safe-self.fn', + ], +}); +function noEvalIf( + needle = '' +) { + if ( typeof needle !== 'string' ) { return; } + const safe = safeSelf(); + const reNeedle = safe.patternToRegex(needle); + window.eval = new Proxy(window.eval, { // jshint ignore: line + apply: function(target, thisArg, args) { + const a = args[0]; + if ( reNeedle.test(a.toString()) ) { return; } + return target.apply(thisArg, args); + } + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'no-fetch-if.js', + aliases: [ + 'prevent-fetch.js', + ], + fn: noFetchIf, + dependencies: [ + 'generate-content.fn', + 'safe-self.fn', + ], +}); +function noFetchIf( + propsToMatch = '', + responseBody = '' +) { + if ( typeof propsToMatch !== 'string' ) { return; } + const safe = safeSelf(); + const needles = []; + for ( const condition of propsToMatch.split(/\s+/) ) { + if ( condition === '' ) { continue; } + const pos = condition.indexOf(':'); + let key, value; + if ( pos !== -1 ) { + key = condition.slice(0, pos); + value = condition.slice(pos + 1); + } else { + key = 'url'; + value = condition; + } + needles.push({ key, re: safe.patternToRegex(value) }); + } + const log = needles.length === 0 ? console.log.bind(console) : undefined; + self.fetch = new Proxy(self.fetch, { + apply: function(target, thisArg, args) { + const details = args[0] instanceof self.Request + ? args[0] + : Object.assign({ url: args[0] }, args[1]); + let proceed = true; + try { + const props = new Map(); + for ( const prop in details ) { + let v = details[prop]; + if ( typeof v !== 'string' ) { + try { v = JSON.stringify(v); } + catch(ex) { } + } + if ( typeof v !== 'string' ) { continue; } + props.set(prop, v); + } + if ( log !== undefined ) { + const out = Array.from(props) + .map(a => `${a[0]}:${a[1]}`) + .join(' '); + log(`uBO: fetch(${out})`); + } + proceed = needles.length === 0; + for ( const { key, re } of needles ) { + if ( + props.has(key) === false || + re.test(props.get(key)) === false + ) { + proceed = true; + break; + } + } + } catch(ex) { + } + if ( proceed ) { + return Reflect.apply(target, thisArg, args); + } + let responseType = ''; + if ( details.mode === undefined || details.mode === 'cors' ) { + try { + const desURL = new URL(details.url); + responseType = desURL.origin !== document.location.origin + ? 'cors' + : 'basic'; + } catch(_) { + } + } + return generateContentFn(responseBody).then(text => { + const response = new Response(text, { + statusText: 'OK', + headers: { + 'Content-Length': text.length, + } + }); + Object.defineProperty(response, 'url', { + value: details.url + }); + if ( responseType !== '' ) { + Object.defineProperty(response, 'type', { + value: responseType + }); + } + return response; + }); + } + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'prevent-refresh.js', + aliases: [ + 'refresh-defuser.js', + ], + fn: preventRefresh, + world: 'ISOLATED', + dependencies: [ + 'run-at.fn', + ], +}); +// https://www.reddit.com/r/uBlockOrigin/comments/q0frv0/while_reading_a_sports_article_i_was_redirected/hf7wo9v/ +function preventRefresh( + arg1 = '' +) { + if ( typeof arg1 !== 'string' ) { return; } + const defuse = ( ) => { + const meta = document.querySelector('meta[http-equiv="refresh" i][content]'); + if ( meta === null ) { return; } + const s = arg1 === '' + ? meta.getAttribute('content') + : arg1; + const ms = Math.max(parseFloat(s) || 0, 0) * 1000; + setTimeout(( ) => { window.stop(); }, ms); + }; + runAt(( ) => { + defuse(); + }, 'interactive'); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'remove-attr.js', + aliases: [ + 'ra.js', + ], + fn: removeAttr, + dependencies: [ + 'run-at.fn', + ], +}); +function removeAttr( + token = '', + selector = '', + behavior = '' +) { + if ( typeof token !== 'string' ) { return; } + if ( token === '' ) { return; } + const tokens = token.split(/\s*\|\s*/); + if ( selector === '' ) { + selector = `[${tokens.join('],[')}]`; + } + let timer; + const rmattr = ( ) => { + timer = undefined; + try { + const nodes = document.querySelectorAll(selector); + for ( const node of nodes ) { + for ( const attr of tokens ) { + node.removeAttribute(attr); + } + } + } catch(ex) { + } + }; + const mutationHandler = mutations => { + if ( timer !== 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; } + timer = self.requestIdleCallback(rmattr, { timeout: 17 }); + }; + 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(); + }, /\bcomplete\b/.test(behavior) ? 'idle' : 'interactive'); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'remove-class.js', + aliases: [ + 'rc.js', + ], + fn: removeClass, + world: 'ISOLATED', + dependencies: [ + 'run-at.fn', + ], +}); +function removeClass( + token = '', + selector = '', + behavior = '' +) { + if ( typeof token !== 'string' ) { return; } + if ( token === '' ) { return; } + const classTokens = token.split(/\s*\|\s*/); + if ( selector === '' ) { + selector = '.' + classTokens.map(a => CSS.escape(a)).join(',.'); + } + const mustStay = /\bstay\b/.test(behavior); + let timer; + const rmclass = function() { + timer = undefined; + try { + const nodes = document.querySelectorAll(selector); + for ( const node of nodes ) { + node.classList.remove(...classTokens); + } + } catch(ex) { + } + if ( mustStay ) { return; } + if ( document.readyState !== 'complete' ) { return; } + observer.disconnect(); + }; + const mutationHandler = mutations => { + if ( timer !== 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; } + timer = self.requestIdleCallback(rmclass, { timeout: 67 }); + }; + const observer = new MutationObserver(mutationHandler); + const start = ( ) => { + rmclass(); + observer.observe(document, { + attributes: true, + attributeFilter: [ 'class' ], + childList: true, + subtree: true, + }); + }; + runAt(( ) => { + start(); + }, /\bcomplete\b/.test(behavior) ? 'idle' : 'loading'); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'no-requestAnimationFrame-if.js', + aliases: [ + 'norafif.js', + 'prevent-requestAnimationFrame.js', + ], + fn: noRequestAnimationFrameIf, + dependencies: [ + 'safe-self.fn', + ], +}); +function noRequestAnimationFrameIf( + needle = '' +) { + if ( typeof needle !== 'string' ) { return; } + const safe = safeSelf(); + const needleNot = needle.charAt(0) === '!'; + if ( needleNot ) { needle = needle.slice(1); } + const log = needleNot === false && needle === '' ? console.log : undefined; + const reNeedle = safe.patternToRegex(needle); + window.requestAnimationFrame = new Proxy(window.requestAnimationFrame, { + apply: function(target, thisArg, args) { + const a = args[0] instanceof Function + ? String(safe.Function_toString(args[0])) + : String(args[0]); + let defuse = false; + if ( log !== undefined ) { + log('uBO: requestAnimationFrame("%s")', a); + } else { + defuse = reNeedle.test(a) !== needleNot; + } + if ( defuse ) { + args[0] = function(){}; + } + return target.apply(thisArg, args); + } + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'set-constant.js', + aliases: [ + 'set.js', + ], + fn: setConstant, + dependencies: [ + 'set-constant-core.fn' + ], +}); +function setConstant( + ...args +) { + setConstantCore(false, ...args); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'no-setInterval-if.js', + aliases: [ + 'nosiif.js', + 'prevent-setInterval.js', + 'setInterval-defuser.js', + ], + fn: noSetIntervalIf, + dependencies: [ + 'safe-self.fn', + ], +}); +function noSetIntervalIf( + needle = '', + delay = '' +) { + if ( typeof needle !== 'string' ) { return; } + const safe = safeSelf(); + const needleNot = needle.charAt(0) === '!'; + if ( needleNot ) { needle = needle.slice(1); } + if ( delay === '' ) { delay = undefined; } + let delayNot = false; + if ( delay !== undefined ) { + delayNot = delay.charAt(0) === '!'; + if ( delayNot ) { delay = delay.slice(1); } + delay = parseInt(delay, 10); + } + const log = needleNot === false && needle === '' && delay === undefined + ? console.log + : undefined; + const reNeedle = safe.patternToRegex(needle); + self.setInterval = new Proxy(self.setInterval, { + apply: function(target, thisArg, args) { + const a = args[0] instanceof Function + ? String(safe.Function_toString(args[0])) + : String(args[0]); + const b = args[1]; + if ( log !== undefined ) { + log('uBO: setInterval("%s", %s)', a, b); + } else { + let defuse; + if ( needle !== '' ) { + defuse = reNeedle.test(a) !== needleNot; + } + if ( defuse !== false && delay !== undefined ) { + defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot; + } + if ( defuse ) { + args[0] = function(){}; + } + } + return Reflect.apply(target, thisArg, args); + }, + get(target, prop, receiver) { + if ( prop === 'toString' ) { + return target.toString.bind(target); + } + return Reflect.get(target, prop, receiver); + }, + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'no-setTimeout-if.js', + aliases: [ + 'nostif.js', + 'prevent-setTimeout.js', + 'setTimeout-defuser.js', + ], + fn: noSetTimeoutIf, + dependencies: [ + 'safe-self.fn', + ], +}); +function noSetTimeoutIf( + needle = '', + delay = '' +) { + if ( typeof needle !== 'string' ) { return; } + const safe = safeSelf(); + const needleNot = needle.charAt(0) === '!'; + if ( needleNot ) { needle = needle.slice(1); } + if ( delay === '' ) { delay = undefined; } + let delayNot = false; + if ( delay !== undefined ) { + delayNot = delay.charAt(0) === '!'; + if ( delayNot ) { delay = delay.slice(1); } + delay = parseInt(delay, 10); + } + const log = needleNot === false && needle === '' && delay === undefined + ? console.log + : undefined; + const reNeedle = safe.patternToRegex(needle); + self.setTimeout = new Proxy(self.setTimeout, { + apply: function(target, thisArg, args) { + const a = args[0] instanceof Function + ? String(safe.Function_toString(args[0])) + : String(args[0]); + const b = args[1]; + if ( log !== undefined ) { + log('uBO: setTimeout("%s", %s)', a, b); + } else { + let defuse; + if ( needle !== '' ) { + defuse = reNeedle.test(a) !== needleNot; + } + if ( defuse !== false && delay !== undefined ) { + defuse = (b === delay || isNaN(b) && isNaN(delay) ) !== delayNot; + } + if ( defuse ) { + args[0] = function(){}; + } + } + return Reflect.apply(target, thisArg, args); + }, + get(target, prop, receiver) { + if ( prop === 'toString' ) { + return target.toString.bind(target); + } + return Reflect.get(target, prop, receiver); + }, + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'webrtc-if.js', + fn: webrtcIf, + dependencies: [ + 'safe-self.fn', + ], +}); +function webrtcIf( + good = '' +) { + if ( typeof good !== 'string' ) { return; } + const safe = safeSelf(); + const reGood = safe.patternToRegex(good); + const rtcName = window.RTCPeerConnection + ? 'RTCPeerConnection' + : (window.webkitRTCPeerConnection ? 'webkitRTCPeerConnection' : ''); + if ( rtcName === '' ) { return; } + const log = console.log.bind(console); + const neuteredPeerConnections = new WeakSet(); + const isGoodConfig = function(instance, config) { + if ( neuteredPeerConnections.has(instance) ) { return false; } + if ( config instanceof Object === false ) { return true; } + if ( Array.isArray(config.iceServers) === false ) { return true; } + for ( const server of config.iceServers ) { + const urls = typeof server.urls === 'string' + ? [ server.urls ] + : server.urls; + if ( Array.isArray(urls) ) { + for ( const url of urls ) { + if ( reGood.test(url) ) { return true; } + } + } + if ( typeof server.username === 'string' ) { + if ( reGood.test(server.username) ) { return true; } + } + if ( typeof server.credential === 'string' ) { + if ( reGood.test(server.credential) ) { return true; } + } + } + neuteredPeerConnections.add(instance); + return false; + }; + const peerConnectionCtor = window[rtcName]; + const peerConnectionProto = peerConnectionCtor.prototype; + peerConnectionProto.createDataChannel = + new Proxy(peerConnectionProto.createDataChannel, { + apply: function(target, thisArg, args) { + if ( isGoodConfig(target, args[1]) === false ) { + log('uBO:', args[1]); + return Reflect.apply(target, thisArg, args.slice(0, 1)); + } + return Reflect.apply(target, thisArg, args); + }, + }); + window[rtcName] = + new Proxy(peerConnectionCtor, { + construct: function(target, args) { + if ( isGoodConfig(target, args[0]) === false ) { + log('uBO:', args[0]); + return Reflect.construct(target); + } + return Reflect.construct(target, args); + } + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'no-xhr-if.js', + aliases: [ + 'prevent-xhr.js', + ], + fn: noXhrIf, + dependencies: [ + 'generate-content.fn', + 'match-object-properties.fn', + 'parse-properties-to-match.fn', + 'safe-self.fn', + ], +}); +function noXhrIf( + propsToMatch = '', + directive = '' +) { + if ( typeof propsToMatch !== 'string' ) { return; } + const xhrInstances = new WeakMap(); + const propNeedles = parsePropertiesToMatch(propsToMatch, 'url'); + const log = propNeedles.size === 0 ? console.log.bind(console) : undefined; + const warOrigin = scriptletGlobals.get('warOrigin'); + const headers = { + 'date': '', + 'content-type': '', + 'content-length': '', + }; + self.XMLHttpRequest = class extends self.XMLHttpRequest { + open(method, url, ...args) { + if ( log !== undefined ) { + log(`uBO: xhr.open(${method}, ${url}, ${args.join(', ')})`); + return super.open(method, url, ...args); + } + xhrInstances.delete(this); + if ( warOrigin !== undefined && url.startsWith(warOrigin) ) { + return super.open(method, url, ...args); + } + const haystack = { method, url }; + if ( matchObjectProperties(propNeedles, haystack) ) { + xhrInstances.set(this, haystack); + } + haystack.headers = Object.assign({}, headers); + return super.open(method, url, ...args); + } + send(...args) { + const haystack = xhrInstances.get(this); + if ( haystack === undefined ) { + return super.send(...args); + } + haystack.headers['date'] = (new Date()).toUTCString(); + let promise = Promise.resolve({ + xhr: this, + directive, + props: { + readyState: { value: 4 }, + response: { value: '' }, + responseText: { value: '' }, + responseXML: { value: null }, + responseURL: { value: haystack.url }, + status: { value: 200 }, + statusText: { value: 'OK' }, + }, + }); + switch ( this.responseType ) { + case 'arraybuffer': + promise = promise.then(details => { + details.props.response.value = new ArrayBuffer(0); + return details; + }); + haystack.headers['content-type'] = 'application/octet-stream'; + break; + case 'blob': + promise = promise.then(details => { + details.props.response.value = new Blob([]); + return details; + }); + haystack.headers['content-type'] = 'application/octet-stream'; + break; + case 'document': { + promise = promise.then(details => { + const parser = new DOMParser(); + const doc = parser.parseFromString('', 'text/html'); + details.props.response.value = doc; + details.props.responseXML.value = doc; + return details; + }); + haystack.headers['content-type'] = 'text/html'; + break; + } + case 'json': + promise = promise.then(details => { + details.props.response.value = {}; + details.props.responseText.value = '{}'; + return details; + }); + haystack.headers['content-type'] = 'application/json'; + break; + default: + if ( directive === '' ) { break; } + promise = promise.then(details => { + return generateContentFn(details.directive).then(text => { + details.props.response.value = text; + details.props.responseText.value = text; + return details; + }); + }); + haystack.headers['content-type'] = 'text/plain'; + break; + } + promise.then(details => { + haystack.headers['content-length'] = `${details.props.response.value}`.length; + Object.defineProperties(details.xhr, details.props); + details.xhr.dispatchEvent(new Event('readystatechange')); + details.xhr.dispatchEvent(new Event('load')); + details.xhr.dispatchEvent(new Event('loadend')); + }); + } + getResponseHeader(headerName) { + const haystack = xhrInstances.get(this); + if ( haystack === undefined || this.readyState < this.HEADERS_RECEIVED ) { + return super.getResponseHeader(headerName); + } + const value = haystack.headers[headerName.toLowerCase()]; + if ( value !== undefined && value !== '' ) { return value; } + return null; + } + getAllResponseHeaders() { + const haystack = xhrInstances.get(this); + if ( haystack === undefined || this.readyState < this.HEADERS_RECEIVED ) { + return super.getAllResponseHeaders(); + } + const out = []; + for ( const [ name, value ] of Object.entries(haystack.headers) ) { + if ( !value ) { continue; } + out.push(`${name}: ${value}`); + } + if ( out.length !== 0 ) { out.push(''); } + return out.join('\r\n'); + } + }; +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'no-window-open-if.js', + aliases: [ + 'nowoif.js', + 'prevent-window-open.js', + 'window.open-defuser.js', + ], + fn: noWindowOpenIf, + dependencies: [ + 'safe-self.fn', + 'should-log.fn', + ], +}); +function noWindowOpenIf( + pattern = '', + delay = '', + decoy = '' +) { + const safe = safeSelf(); + const targetMatchResult = pattern.startsWith('!') === false; + if ( targetMatchResult === false ) { + pattern = pattern.slice(1); + } + const rePattern = safe.patternToRegex(pattern); + let autoRemoveAfter = parseInt(delay); + if ( isNaN(autoRemoveAfter) ) { + autoRemoveAfter = -1; + } + const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); + const logLevel = shouldLog(extraArgs); + const createDecoy = function(tag, urlProp, url) { + const decoyElem = document.createElement(tag); + decoyElem[urlProp] = url; + decoyElem.style.setProperty('height','1px', 'important'); + decoyElem.style.setProperty('position','fixed', 'important'); + decoyElem.style.setProperty('top','-1px', 'important'); + decoyElem.style.setProperty('width','1px', 'important'); + document.body.appendChild(decoyElem); + setTimeout(( ) => { decoyElem.remove(); }, autoRemoveAfter * 1000); + return decoyElem; + }; + window.open = new Proxy(window.open, { + apply: function(target, thisArg, args) { + const haystack = args.join(' '); + if ( logLevel ) { + safe.uboLog('window.open:', haystack); + } + if ( rePattern.test(haystack) !== targetMatchResult ) { + return Reflect.apply(target, thisArg, args); + } + if ( autoRemoveAfter < 0 ) { return null; } + const decoyElem = decoy === 'obj' + ? createDecoy('object', 'data', ...args) + : createDecoy('iframe', 'src', ...args); + let popup = decoyElem.contentWindow; + if ( typeof popup === 'object' && popup !== null ) { + Object.defineProperty(popup, 'closed', { value: false }); + } else { + const noopFunc = (function(){}).bind(self); + popup = new Proxy(self, { + get: function(target, prop) { + if ( prop === 'closed' ) { return false; } + const r = Reflect.get(...arguments); + if ( typeof r === 'function' ) { return noopFunc; } + return target[prop]; + }, + set: function() { + return Reflect.set(...arguments); + }, + }); + } + if ( logLevel ) { + popup = new Proxy(popup, { + get: function(target, prop) { + safe.uboLog('window.open / get', prop, '===', target[prop]); + return Reflect.get(...arguments); + }, + set: function(target, prop, value) { + safe.uboLog('window.open / set', prop, '=', value); + return Reflect.set(...arguments); + }, + }); + } + return popup; + } + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'close-window.js', + aliases: [ + 'window-close-if.js', + ], + fn: closeWindow, + world: 'ISOLATED', + dependencies: [ + 'safe-self.fn', + ], +}); +// https://github.com/uBlockOrigin/uAssets/issues/10323#issuecomment-992312847 +// https://github.com/AdguardTeam/Scriptlets/issues/158 +// https://github.com/uBlockOrigin/uBlock-issues/discussions/2270 +function closeWindow( + arg1 = '' +) { + if ( typeof arg1 !== 'string' ) { return; } + const safe = safeSelf(); + let subject = ''; + if ( /^\/.*\/$/.test(arg1) ) { + subject = window.location.href; + } else if ( arg1 !== '' ) { + subject = `${window.location.pathname}${window.location.search}`; + } + try { + const re = safe.patternToRegex(arg1); + if ( re.test(subject) ) { + window.close(); + } + } catch(ex) { + console.log(ex); + } +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'window.name-defuser.js', + fn: windowNameDefuser, +}); +// https://github.com/gorhill/uBlock/issues/1228 +function windowNameDefuser() { + if ( window === window.top ) { + window.name = ''; + } +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'overlay-buster.js', + fn: overlayBuster, +}); +// Experimental: Generic nuisance overlay buster. +// if this works well and proves to be useful, this may end up +// as a stock tool in uBO's popup panel. +function overlayBuster() { + if ( window !== window.top ) { return; } + var tstart; + var ttl = 30000; + var delay = 0; + var delayStep = 50; + var buster = function() { + var docEl = document.documentElement, + bodyEl = document.body, + vw = Math.min(docEl.clientWidth, window.innerWidth), + vh = Math.min(docEl.clientHeight, window.innerHeight), + tol = Math.min(vw, vh) * 0.05, + el = document.elementFromPoint(vw/2, vh/2), + style, rect; + for (;;) { + if ( el === null || el.parentNode === null || el === bodyEl ) { + break; + } + style = window.getComputedStyle(el); + if ( parseInt(style.zIndex, 10) >= 1000 || style.position === 'fixed' ) { + rect = el.getBoundingClientRect(); + if ( rect.left <= tol && rect.top <= tol && (vw - rect.right) <= tol && (vh - rect.bottom) < tol ) { + el.parentNode.removeChild(el); + tstart = Date.now(); + el = document.elementFromPoint(vw/2, vh/2); + bodyEl.style.setProperty('overflow', 'auto', 'important'); + docEl.style.setProperty('overflow', 'auto', 'important'); + continue; + } + } + el = el.parentNode; + } + if ( (Date.now() - tstart) < ttl ) { + delay = Math.min(delay + delayStep, 1000); + setTimeout(buster, delay); + } + }; + var domReady = function(ev) { + if ( ev ) { + document.removeEventListener(ev.type, domReady); + } + tstart = Date.now(); + setTimeout(buster, delay); + }; + if ( document.readyState === 'loading' ) { + document.addEventListener('DOMContentLoaded', domReady); + } else { + domReady(); + } +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'alert-buster.js', + fn: alertBuster, +}); +// https://github.com/uBlockOrigin/uAssets/issues/8 +function alertBuster() { + window.alert = new Proxy(window.alert, { + apply: function(a) { + console.info(a); + }, + get(target, prop, receiver) { + if ( prop === 'toString' ) { + return target.toString.bind(target); + } + return Reflect.get(target, prop, receiver); + }, + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'nowebrtc.js', + fn: noWebrtc, +}); +// Prevent web pages from using RTCPeerConnection(), and report attempts in console. +function noWebrtc() { + var rtcName = window.RTCPeerConnection ? 'RTCPeerConnection' : ( + window.webkitRTCPeerConnection ? 'webkitRTCPeerConnection' : '' + ); + if ( rtcName === '' ) { return; } + var log = console.log.bind(console); + var pc = function(cfg) { + log('Document tried to create an RTCPeerConnection: %o', cfg); + }; + const noop = function() { + }; + pc.prototype = { + close: noop, + createDataChannel: noop, + createOffer: noop, + setRemoteDescription: noop, + toString: function() { + return '[object RTCPeerConnection]'; + } + }; + var z = window[rtcName]; + window[rtcName] = pc.bind(window); + if ( z.prototype ) { + z.prototype.createDataChannel = function() { + return { + close: function() {}, + send: function() {} + }; + }.bind(null); + } +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'golem.de.js', + fn: golemDe, +}); +// https://github.com/uBlockOrigin/uAssets/issues/88 +function golemDe() { + const rael = window.addEventListener; + window.addEventListener = function(a, b) { + rael(...arguments); + let haystack; + try { + haystack = b.toString(); + } catch(ex) { + } + if ( + typeof haystack === 'string' && + /^\s*function\s*\(\)\s*\{\s*window\.clearTimeout\(r\)\s*\}\s*$/.test(haystack) + ) { + b(); + } + }.bind(window); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'disable-newtab-links.js', + fn: disableNewtabLinks, +}); +// https://github.com/uBlockOrigin/uAssets/issues/913 +function disableNewtabLinks() { + document.addEventListener('click', function(ev) { + var target = ev.target; + while ( target !== null ) { + if ( target.localName === 'a' && target.hasAttribute('target') ) { + ev.stopPropagation(); + ev.preventDefault(); + break; + } + target = target.parentNode; + } + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'remove-cookie.js', + aliases: [ + 'cookie-remover.js', + ], + fn: cookieRemover, + world: 'ISOLATED', + dependencies: [ + 'safe-self.fn', + ], +}); +// https://github.com/NanoAdblocker/NanoFilters/issues/149 +function cookieRemover( + needle = '' +) { + if ( typeof needle !== 'string' ) { return; } + const safe = safeSelf(); + const reName = safe.patternToRegex(needle); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 1); + const throttle = (fn, ms = 500) => { + if ( throttle.timer !== undefined ) { return; } + throttle.timer = setTimeout(( ) => { + throttle.timer = undefined; + fn(); + }, ms); + }; + const removeCookie = ( ) => { + document.cookie.split(';').forEach(cookieStr => { + const pos = cookieStr.indexOf('='); + if ( pos === -1 ) { return; } + const cookieName = cookieStr.slice(0, pos).trim(); + if ( reName.test(cookieName) === false ) { return; } + const part1 = cookieName + '='; + const part2a = '; domain=' + document.location.hostname; + const part2b = '; domain=.' + document.location.hostname; + let part2c, part2d; + const domain = document.domain; + if ( domain ) { + if ( domain !== document.location.hostname ) { + part2c = '; domain=.' + domain; + } + if ( domain.startsWith('www.') ) { + part2d = '; domain=' + domain.replace('www', ''); + } + } + const part3 = '; path=/'; + const part4 = '; Max-Age=-1000; expires=Thu, 01 Jan 1970 00:00:00 GMT'; + document.cookie = part1 + part4; + document.cookie = part1 + part2a + part4; + document.cookie = part1 + part2b + part4; + document.cookie = part1 + part3 + part4; + document.cookie = part1 + part2a + part3 + part4; + document.cookie = part1 + part2b + part3 + part4; + if ( part2c !== undefined ) { + document.cookie = part1 + part2c + part3 + part4; + } + if ( part2d !== undefined ) { + document.cookie = part1 + part2d + part3 + part4; + } + }); + }; + removeCookie(); + window.addEventListener('beforeunload', removeCookie); + if ( typeof extraArgs.when !== 'string' ) { return; } + const supportedEventTypes = [ 'scroll', 'keydown' ]; + const eventTypes = extraArgs.when.split(/\s/); + for ( const type of eventTypes ) { + if ( supportedEventTypes.includes(type) === false ) { continue; } + document.addEventListener(type, ( ) => { + throttle(removeCookie); + }, { passive: true }); + } +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'xml-prune.js', + fn: xmlPrune, + dependencies: [ + 'safe-self.fn', + 'should-log.fn', + ], +}); +function xmlPrune( + selector = '', + selectorCheck = '', + urlPattern = '' +) { + if ( typeof selector !== 'string' ) { return; } + if ( selector === '' ) { return; } + const safe = safeSelf(); + const reUrl = safe.patternToRegex(urlPattern); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); + const log = shouldLog(extraArgs) ? ((...args) => { safe.uboLog(...args); }) : (( ) => { }); + const queryAll = (xmlDoc, selector) => { + const isXpath = /^xpath\(.+\)$/.test(selector); + if ( isXpath === false ) { + return Array.from(xmlDoc.querySelectorAll(selector)); + } + const xpr = xmlDoc.evaluate( + selector.slice(6, -1), + xmlDoc, + null, + XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, + null + ); + const out = []; + for ( let i = 0; i < xpr.snapshotLength; i++ ) { + const node = xpr.snapshotItem(i); + out.push(node); + } + return out; + }; + const pruneFromDoc = xmlDoc => { + try { + if ( selectorCheck !== '' && xmlDoc.querySelector(selectorCheck) === null ) { + return xmlDoc; + } + if ( extraArgs.logdoc ) { + const serializer = new XMLSerializer(); + log(`xmlPrune: document is\n\t${serializer.serializeToString(xmlDoc)}`); + } + const items = queryAll(xmlDoc, selector); + if ( items.length === 0 ) { return xmlDoc; } + log(`xmlPrune: removing ${items.length} items`); + for ( const item of items ) { + if ( item.nodeType === 1 ) { + item.remove(); + } else if ( item.nodeType === 2 ) { + item.ownerElement.removeAttribute(item.nodeName); + } + log(`xmlPrune: ${item.constructor.name}.${item.nodeName} removed`); + } + } catch(ex) { + log(ex); + } + return xmlDoc; + }; + const pruneFromText = text => { + if ( (/^\s*</.test(text) && />\s*$/.test(text)) === false ) { + return text; + } + try { + const xmlParser = new DOMParser(); + const xmlDoc = xmlParser.parseFromString(text, 'text/xml'); + pruneFromDoc(xmlDoc); + const serializer = new XMLSerializer(); + text = serializer.serializeToString(xmlDoc); + } catch(ex) { + } + return text; + }; + const urlFromArg = arg => { + if ( typeof arg === 'string' ) { return arg; } + if ( arg instanceof Request ) { return arg.url; } + return String(arg); + }; + self.fetch = new Proxy(self.fetch, { + apply: function(target, thisArg, args) { + const fetchPromise = Reflect.apply(target, thisArg, args); + if ( reUrl.test(urlFromArg(args[0])) === false ) { + return fetchPromise; + } + return fetchPromise.then(responseBefore => { + const response = responseBefore.clone(); + return response.text().then(text => { + const responseAfter = new Response(pruneFromText(text), { + status: responseBefore.status, + statusText: responseBefore.statusText, + headers: responseBefore.headers, + }); + Object.defineProperties(responseAfter, { + ok: { value: responseBefore.ok }, + redirected: { value: responseBefore.redirected }, + type: { value: responseBefore.type }, + url: { value: responseBefore.url }, + }); + return responseAfter; + }).catch(( ) => + responseBefore + ); + }); + } + }); + self.XMLHttpRequest.prototype.open = new Proxy(self.XMLHttpRequest.prototype.open, { + apply: async (target, thisArg, args) => { + if ( reUrl.test(urlFromArg(args[1])) === false ) { + return Reflect.apply(target, thisArg, args); + } + thisArg.addEventListener('readystatechange', function() { + if ( thisArg.readyState !== 4 ) { return; } + const type = thisArg.responseType; + if ( + type === 'document' || + type === '' && thisArg.responseXML instanceof XMLDocument + ) { + pruneFromDoc(thisArg.responseXML); + const serializer = new XMLSerializer(); + const textout = serializer.serializeToString(thisArg.responseXML); + Object.defineProperty(thisArg, 'responseText', { value: textout }); + return; + } + if ( + type === 'text' || + type === '' && typeof thisArg.responseText === 'string' + ) { + const textin = thisArg.responseText; + const textout = pruneFromText(textin); + if ( textout === textin ) { return; } + Object.defineProperty(thisArg, 'response', { value: textout }); + Object.defineProperty(thisArg, 'responseText', { value: textout }); + return; + } + }); + return Reflect.apply(target, thisArg, args); + } + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'm3u-prune.js', + fn: m3uPrune, + dependencies: [ + 'safe-self.fn', + 'should-log.fn', + ], +}); +// https://en.wikipedia.org/wiki/M3U +function m3uPrune( + m3uPattern = '', + urlPattern = '' +) { + if ( typeof m3uPattern !== 'string' ) { return; } + const safe = safeSelf(); + const options = safe.getExtraArgs(Array.from(arguments), 2); + const logLevel = shouldLog(options); + const uboLog = logLevel ? ((...args) => safe.uboLog(...args)) : (( ) => { }); + const regexFromArg = arg => { + if ( arg === '' ) { return /^/; } + const match = /^\/(.+)\/([gms]*)$/.exec(arg); + if ( match !== null ) { + let flags = match[2] || ''; + if ( flags.includes('m') ) { flags += 's'; } + return new RegExp(match[1], flags); + } + return new RegExp( + arg.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*+/g, '.*?') + ); + }; + const reM3u = regexFromArg(m3uPattern); + const reUrl = regexFromArg(urlPattern); + const pruneSpliceoutBlock = (lines, i) => { + if ( lines[i].startsWith('#EXT-X-CUE:TYPE="SpliceOut"') === false ) { + return false; + } + uboLog('m3u-prune: discarding', `\n\t${lines[i]}`); + lines[i] = undefined; i += 1; + if ( lines[i].startsWith('#EXT-X-ASSET:CAID') ) { + uboLog(`\t${lines[i]}`); + lines[i] = undefined; i += 1; + } + if ( lines[i].startsWith('#EXT-X-SCTE35:') ) { + uboLog(`\t${lines[i]}`); + lines[i] = undefined; i += 1; + } + if ( lines[i].startsWith('#EXT-X-CUE-IN') ) { + uboLog(`\t${lines[i]}`); + lines[i] = undefined; i += 1; + } + if ( lines[i].startsWith('#EXT-X-SCTE35:') ) { + uboLog(`\t${lines[i]}`); + lines[i] = undefined; i += 1; + } + return true; + }; + const pruneInfBlock = (lines, i) => { + if ( lines[i].startsWith('#EXTINF') === false ) { return false; } + if ( reM3u.test(lines[i+1]) === false ) { return false; } + uboLog('m3u-prune: discarding', `\n\t${lines[i]}, \n\t${lines[i+1]}`); + lines[i] = lines[i+1] = undefined; i += 2; + if ( lines[i].startsWith('#EXT-X-DISCONTINUITY') ) { + uboLog(`\t${lines[i]}`); + lines[i] = undefined; i += 1; + } + return true; + }; + const pruner = text => { + if ( (/^\s*#EXTM3U/.test(text)) === false ) { return text; } + if ( reM3u.multiline ) { + reM3u.lastIndex = 0; + for (;;) { + const match = reM3u.exec(text); + if ( match === null ) { break; } + let discard = match[0]; + let before = text.slice(0, match.index); + if ( + /^[\n\r]+/.test(discard) === false && + /[\n\r]+$/.test(before) === false + ) { + const startOfLine = /[^\n\r]+$/.exec(before); + if ( startOfLine !== null ) { + before = before.slice(0, startOfLine.index); + discard = startOfLine[0] + discard; + } + } + let after = text.slice(match.index + match[0].length); + if ( + /[\n\r]+$/.test(discard) === false && + /^[\n\r]+/.test(after) === false + ) { + const endOfLine = /^[^\n\r]+/.exec(after); + if ( endOfLine !== null ) { + after = after.slice(endOfLine.index); + discard += discard + endOfLine[0]; + } + } + text = before.trim() + '\n' + after.trim(); + reM3u.lastIndex = before.length + 1; + uboLog('m3u-prune: discarding\n', + discard.split(/\n+/).map(s => `\t${s}`).join('\n') + ); + if ( reM3u.global === false ) { break; } + } + return text; + } + const lines = text.split(/\n\r|\n|\r/); + for ( let i = 0; i < lines.length; i++ ) { + if ( lines[i] === undefined ) { continue; } + if ( pruneSpliceoutBlock(lines, i) ) { continue; } + if ( pruneInfBlock(lines, i) ) { continue; } + } + return lines.filter(l => l !== undefined).join('\n'); + }; + const urlFromArg = arg => { + if ( typeof arg === 'string' ) { return arg; } + if ( arg instanceof Request ) { return arg.url; } + return String(arg); + }; + const realFetch = self.fetch; + self.fetch = new Proxy(self.fetch, { + apply: function(target, thisArg, args) { + if ( reUrl.test(urlFromArg(args[0])) === false ) { + return Reflect.apply(target, thisArg, args); + } + return realFetch(...args).then(realResponse => + realResponse.text().then(text => + new Response(pruner(text), { + status: realResponse.status, + statusText: realResponse.statusText, + headers: realResponse.headers, + }) + ) + ); + } + }); + self.XMLHttpRequest.prototype.open = new Proxy(self.XMLHttpRequest.prototype.open, { + apply: async (target, thisArg, args) => { + if ( reUrl.test(urlFromArg(args[1])) === false ) { + return Reflect.apply(target, thisArg, args); + } + thisArg.addEventListener('readystatechange', function() { + if ( thisArg.readyState !== 4 ) { return; } + const type = thisArg.responseType; + if ( type !== '' && type !== 'text' ) { return; } + const textin = thisArg.responseText; + const textout = pruner(textin); + if ( textout === textin ) { return; } + Object.defineProperty(thisArg, 'response', { value: textout }); + Object.defineProperty(thisArg, 'responseText', { value: textout }); + }); + return Reflect.apply(target, thisArg, args); + } + }); +} + +/******************************************************************************* + * + * @scriptlet href-sanitizer + * + * @description + * Set the `href` attribute to a value found in the DOM at, or below the + * targeted `a` element. + * + * ### Syntax + * + * ```text + * example.org##+js(href-sanitizer, selector [, source]) + * ``` + * + * - `selector`: required, CSS selector, specifies `a` elements for which the + * `href` attribute must be overridden. + * - `source`: optional, default to `text`, specifies from where to get the + * value which will override the `href` attribute. + * - `text`: the value will be the first valid URL found in the text + * content of the targeted `a` element. + * - `[attr]`: the value will be the attribute _attr_ of the targeted `a` + * element. + * - `?param`: the value will be the query parameter _param_ of the URL + * found in the `href` attribute of the targeted `a` element. + * + * ### Examples + * + * example.org##+js(href-sanitizer, a) + * example.org##+js(href-sanitizer, a[title], [title]) + * example.org##+js(href-sanitizer, a[href*="/away.php?to="], ?to) + * + * */ + +builtinScriptlets.push({ + name: 'href-sanitizer.js', + fn: hrefSanitizer, + world: 'ISOLATED', + dependencies: [ + 'run-at.fn', + ], +}); +function hrefSanitizer( + selector = '', + source = '' +) { + if ( typeof selector !== 'string' ) { return; } + if ( selector === '' ) { return; } + if ( source === '' ) { source = 'text'; } + const sanitizeCopycats = (href, text) => { + let elems = []; + try { + elems = document.querySelectorAll(`a[href="${href}"`); + } + catch(ex) { + } + for ( const elem of elems ) { + elem.setAttribute('href', text); + } + }; + const validateURL = text => { + if ( text === '' ) { return ''; } + if ( /[^\x21-\x7e]/.test(text) ) { return ''; } + try { + const url = new URL(text, document.location); + return url.href; + } catch(ex) { + } + return ''; + }; + const extractText = (elem, source) => { + if ( /^\[.*\]$/.test(source) ) { + return elem.getAttribute(source.slice(1,-1).trim()) || ''; + } + if ( source.startsWith('?') ) { + try { + const url = new URL(elem.href, document.location); + return url.searchParams.get(source.slice(1)) || ''; + } catch(x) { + } + return ''; + } + if ( source === 'text' ) { + return elem.textContent + .replace(/^[^\x21-\x7e]+/, '') // remove leading invalid characters + .replace(/[^\x21-\x7e]+$/, '') // remove trailing invalid characters + ; + } + return ''; + }; + const sanitize = ( ) => { + let elems = []; + try { + elems = document.querySelectorAll(selector); + } + catch(ex) { + return false; + } + for ( const elem of elems ) { + if ( elem.localName !== 'a' ) { continue; } + if ( elem.hasAttribute('href') === false ) { continue; } + const href = elem.getAttribute('href'); + const text = extractText(elem, source); + const hrefAfter = validateURL(text); + if ( hrefAfter === '' ) { continue; } + if ( hrefAfter === href ) { continue; } + elem.setAttribute('href', hrefAfter); + sanitizeCopycats(href, hrefAfter); + } + return true; + }; + let observer, timer; + const onDomChanged = mutations => { + if ( timer !== undefined ) { return; } + let shouldSanitize = false; + for ( const mutation of mutations ) { + if ( mutation.addedNodes.length === 0 ) { continue; } + for ( const node of mutation.addedNodes ) { + if ( node.nodeType !== 1 ) { continue; } + shouldSanitize = true; + break; + } + if ( shouldSanitize ) { break; } + } + if ( shouldSanitize === false ) { return; } + timer = self.requestAnimationFrame(( ) => { + timer = undefined; + sanitize(); + }); + }; + const start = ( ) => { + if ( sanitize() === false ) { return; } + observer = new MutationObserver(onDomChanged); + observer.observe(document.body, { + subtree: true, + childList: true, + }); + }; + runAt(( ) => { start(); }, 'interactive'); +} + +/******************************************************************************* + * + * @scriptlet call-nothrow + * + * @description + * Prevent a function call from throwing. The function will be called, however + * should it throw, the scriptlet will silently process the exception and + * returns as if no exception has occurred. + * + * ### Syntax + * + * ```text + * example.org##+js(call-nothrow, propertyChain) + * ``` + * + * - `propertyChain`: a chain of dot-separated properties which leads to the + * function to be trapped. + * + * ### Examples + * + * example.org##+js(call-nothrow, Object.defineProperty) + * + * */ + +builtinScriptlets.push({ + name: 'call-nothrow.js', + fn: callNothrow, +}); +function callNothrow( + chain = '' +) { + if ( typeof chain !== 'string' ) { return; } + if ( chain === '' ) { return; } + const parts = chain.split('.'); + let owner = window, prop; + for (;;) { + prop = parts.shift(); + if ( parts.length === 0 ) { break; } + owner = owner[prop]; + if ( owner instanceof Object === false ) { return; } + } + if ( prop === '' ) { return; } + const fn = owner[prop]; + if ( typeof fn !== 'function' ) { return; } + owner[prop] = new Proxy(fn, { + apply: function(...args) { + let r; + try { + r = Reflect.apply(...args); + } catch(ex) { + } + return r; + }, + }); +} + + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'spoof-css.js', + fn: spoofCSS, + dependencies: [ + 'safe-self.fn', + ], +}); +function spoofCSS( + selector, + ...args +) { + if ( typeof selector !== 'string' ) { return; } + if ( selector === '' ) { return; } + const toCamelCase = s => s.replace(/-[a-z]/g, s => s.charAt(1).toUpperCase()); + const propToValueMap = new Map(); + for ( let i = 0; i < args.length; i += 2 ) { + if ( typeof args[i+0] !== 'string' ) { break; } + if ( args[i+0] === '' ) { break; } + if ( typeof args[i+1] !== 'string' ) { break; } + propToValueMap.set(toCamelCase(args[i+0]), args[i+1]); + } + const safe = safeSelf(); + const canDebug = scriptletGlobals.has('canDebug'); + const shouldDebug = canDebug && propToValueMap.get('debug') || 0; + const shouldLog = canDebug && propToValueMap.has('log') || 0; + const spoofStyle = (prop, real) => { + const normalProp = toCamelCase(prop); + const shouldSpoof = propToValueMap.has(normalProp); + const value = shouldSpoof ? propToValueMap.get(normalProp) : real; + if ( shouldLog === 2 || shouldSpoof && shouldLog === 1 ) { + safe.uboLog(prop, value); + } + return value; + }; + self.getComputedStyle = new Proxy(self.getComputedStyle, { + apply: function(target, thisArg, args) { + if ( shouldDebug !== 0 ) { debugger; } // jshint ignore: line + const style = Reflect.apply(target, thisArg, args); + const targetElements = new WeakSet(document.querySelectorAll(selector)); + if ( targetElements.has(args[0]) === false ) { return style; } + const proxiedStyle = new Proxy(style, { + get(target, prop, receiver) { + if ( typeof target[prop] === 'function' ) { + if ( prop === 'getPropertyValue' ) { + return (function(prop) { + return spoofStyle(prop, target[prop]); + }).bind(target); + } + return target[prop].bind(target); + } + return spoofStyle(prop, Reflect.get(target, prop, receiver)); + }, + getOwnPropertyDescriptor(target, prop) { + if ( propToValueMap.has(prop) ) { + return { + configurable: true, + enumerable: true, + value: propToValueMap.get(prop), + writable: true, + }; + } + return Reflect.getOwnPropertyDescriptor(target, prop); + }, + }); + return proxiedStyle; + }, + get(target, prop, receiver) { + if ( prop === 'toString' ) { + return target.toString.bind(target); + } + return Reflect.get(target, prop, receiver); + }, + }); + Element.prototype.getBoundingClientRect = new Proxy(Element.prototype.getBoundingClientRect, { + apply: function(target, thisArg, args) { + if ( shouldDebug !== 0 ) { debugger; } // jshint ignore: line + const rect = Reflect.apply(target, thisArg, args); + const targetElements = new WeakSet(document.querySelectorAll(selector)); + if ( targetElements.has(thisArg) === false ) { return rect; } + let { height, width } = rect; + if ( propToValueMap.has('width') ) { + width = parseFloat(propToValueMap.get('width')); + } + if ( propToValueMap.has('height') ) { + height = parseFloat(propToValueMap.get('height')); + } + return new self.DOMRect(rect.x, rect.y, width, height); + }, + get(target, prop, receiver) { + if ( prop === 'toString' ) { + return target.toString.bind(target); + } + return Reflect.get(target, prop, receiver); + }, + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'remove-node-text.js', + aliases: [ + 'rmnt.js', + ], + fn: removeNodeText, + world: 'ISOLATED', + dependencies: [ + 'replace-node-text.fn', + ], +}); +function removeNodeText( + nodeName, + condition, + ...extraArgs +) { + replaceNodeTextFn(nodeName, '', '', 'condition', condition || '', ...extraArgs); +} + +/******************************************************************************* + * + * set-cookie.js + * + * Set specified cookie to a specific value. + * + * Reference: + * https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-cookie.js + * + **/ + +builtinScriptlets.push({ + name: 'set-cookie.js', + fn: setCookie, + world: 'ISOLATED', + dependencies: [ + 'safe-self.fn', + 'set-cookie.fn', + ], +}); +function setCookie( + name = '', + value = '', + path = '' +) { + if ( name === '' ) { return; } + name = encodeURIComponent(name); + + const validValues = [ + 'accept', 'reject', + 'accepted', 'rejected', 'notaccepted', + 'allow', 'deny', + 'allowed', 'disallow', + 'enable', 'disable', + 'enabled', 'disabled', + 'ok', + 'on', 'off', + 'true', 't', 'false', 'f', + 'yes', 'y', 'no', 'n', + 'necessary', 'required', + ]; + const normalized = value.toLowerCase(); + const match = /^("?)(.+)\1$/.exec(normalized); + const unquoted = match && match[2] || normalized; + if ( validValues.includes(unquoted) === false ) { + if ( /^\d+$/.test(unquoted) === false ) { return; } + const n = parseInt(value, 10); + if ( n > 15 ) { return; } + } + + setCookieFn( + false, + name, + value, + '', + path, + safeSelf().getExtraArgs(Array.from(arguments), 3) + ); +} + +// For compatibility with AdGuard +builtinScriptlets.push({ + name: 'set-cookie-reload.js', + fn: setCookieReload, + world: 'ISOLATED', + dependencies: [ + 'set-cookie.js', + ], +}); +function setCookieReload(name, value, path, ...args) { + setCookie(name, value, path, 'reload', '1', ...args); +} + +/******************************************************************************* + * + * set-local-storage-item.js + * set-session-storage-item.js + * + * Set a local/session storage entry to a specific, allowed value. + * + * Reference: + * https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-local-storage-item.js + * https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/set-session-storage-item.js + * + **/ + +builtinScriptlets.push({ + name: 'set-local-storage-item.js', + fn: setLocalStorageItem, + world: 'ISOLATED', + dependencies: [ + 'set-local-storage-item.fn', + ], +}); +function setLocalStorageItem(key = '', value = '') { + setLocalStorageItemFn('local', false, key, value); +} + +builtinScriptlets.push({ + name: 'set-session-storage-item.js', + fn: setSessionStorageItem, + world: 'ISOLATED', + dependencies: [ + 'set-local-storage-item.fn', + ], +}); +function setSessionStorageItem(key = '', value = '') { + setLocalStorageItemFn('session', false, key, value); +} + +/******************************************************************************* + * + * @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 + * + * ### Syntax + * + * ```text + * example.org##+js(set-attr, selector, attr [, value]) + * ``` + * + * - `selector`: CSS selector of DOM elements for which the attribute `attr` + * must be modified. + * - `attr`: the name of the attribute to modify + * - `value`: the value to assign to the target attribute. Possible values: + * - `''`: empty string (default) + * - `true` + * - `false` + * - positive decimal integer 0 <= value < 32768 + * - `[other]`: copy the value from attribute `other` on the same element + * */ + +builtinScriptlets.push({ + name: 'set-attr.js', + fn: setAttr, + world: 'ISOLATED', + dependencies: [ + 'run-at.fn', + ], +}); +function setAttr( + selector = '', + attr = '', + value = '' +) { + if ( typeof selector !== 'string' ) { return; } + if ( selector === '' ) { return; } + + const validValues = [ '', 'false', 'true' ]; + let copyFrom = ''; + + 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) ) { + copyFrom = value.slice(1, -1); + } else { + return; + } + } + + const extractValue = elem => { + if ( copyFrom !== '' ) { + return elem.getAttribute(copyFrom) || ''; + } + return value; + }; + + const applySetAttr = ( ) => { + const elems = []; + try { + elems.push(...document.querySelectorAll(selector)); + } + catch(ex) { + return false; + } + for ( const elem of elems ) { + const before = elem.getAttribute(attr); + const after = extractValue(elem); + if ( after === before ) { continue; } + elem.setAttribute(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'); +} + +/******************************************************************************* + * + * @scriptlet prevent-canvas + * + * @description + * Prevent usage of specific or all (default) canvas APIs. + * + * ### Syntax + * + * ```text + * example.com##+js(prevent-canvas [, contextType]) + * ``` + * + * - `contextType`: A specific type of canvas API to prevent (default to all + * APIs). Can be a string or regex which will be matched against the type + * used in getContext() call. Prepend with `!` to test for no-match. + * + * ### Examples + * + * 1. Prevent `example.com` from accessing all canvas APIs + * + * ```adblock + * example.com##+js(prevent-canvas) + * ``` + * + * 2. Prevent access to any flavor of WebGL API, everywhere + * + * ```adblock + * *##+js(prevent-canvas, /webgl/) + * ``` + * + * 3. Prevent `example.com` from accessing any flavor of canvas API except `2d` + * + * ```adblock + * example.com##+js(prevent-canvas, !2d) + * ``` + * + * ### References + * + * https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext + * + * */ + +builtinScriptlets.push({ + name: 'prevent-canvas.js', + fn: preventCanvas, + dependencies: [ + 'safe-self.fn', + ], +}); +function preventCanvas( + contextType = '' +) { + const safe = safeSelf(); + const pattern = safe.initPattern(contextType, { canNegate: true }); + const proto = globalThis.HTMLCanvasElement.prototype; + proto.getContext = new Proxy(proto.getContext, { + apply(target, thisArg, args) { + if ( safe.testPattern(pattern, args[0]) ) { return null; } + return Reflect.apply(target, thisArg, args); + } + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'multiup.js', + fn: multiup, + world: 'ISOLATED', +}); +function multiup() { + const handler = ev => { + const target = ev.target; + if ( target.matches('button[link]') === false ) { return; } + const ancestor = target.closest('form'); + if ( ancestor === null ) { return; } + if ( ancestor !== target.parentElement ) { return; } + const link = (target.getAttribute('link') || '').trim(); + if ( link === '' ) { return; } + ev.preventDefault(); + ev.stopPropagation(); + document.location.href = link; + }; + document.addEventListener('click', handler, { capture: true }); +} + + +/******************************************************************************* + * + * Scriplets below this section are only available for filter lists from + * trusted sources. They all have the property `requiresTrust` set to `true`. + * + * Trusted sources are: + * + * - uBO's own filter lists, which name starts with "uBlock filters – ", and + * maintained at: https://github.com/uBlockOrigin/uAssets + * + * - The user's own filters as seen in "My filters" pane in uBO's dashboard. + * + * The trustworthiness of filters using these privileged scriptlets are + * evaluated at filter list compiled time: when a filter using one of the + * privileged scriptlet originates from a non-trusted filter list source, it + * is discarded at compile time, specifically from within: + * + * - Source: ./src/js/scriptlet-filtering.js + * - Method: scriptletFilteringEngine.compile(), via normalizeRawFilter() + * + **/ + +/******************************************************************************* + * + * replace-node-text.js + * + * Replace text instance(s) with another text instance inside specific + * DOM nodes. By default, the scriplet stops and quits at the interactive + * stage of a document. + * + * See commit messages for usage: + * - https://github.com/gorhill/uBlock/commit/99ce027fd702 + * - https://github.com/gorhill/uBlock/commit/41876336db48 + * + **/ + +builtinScriptlets.push({ + name: 'trusted-replace-node-text.js', + requiresTrust: true, + aliases: [ + 'trusted-rpnt.js', + 'replace-node-text.js', + 'rpnt.js', + ], + fn: replaceNodeText, + world: 'ISOLATED', + dependencies: [ + 'replace-node-text.fn', + ], +}); +function replaceNodeText( + nodeName, + pattern, + replacement, + ...extraArgs +) { + replaceNodeTextFn(nodeName, pattern, replacement, ...extraArgs); +} + +/******************************************************************************* + * + * trusted-set-constant.js + * + * Set specified property to any value. This is essentially the same as + * set-constant.js, but with no restriction as to which values can be used. + * + **/ + +builtinScriptlets.push({ + name: 'trusted-set-constant.js', + requiresTrust: true, + aliases: [ + 'trusted-set.js', + ], + fn: trustedSetConstant, + dependencies: [ + 'set-constant-core.fn' + ], +}); +function trustedSetConstant( + ...args +) { + setConstantCore(true, ...args); +} + +/******************************************************************************* + * + * trusted-set-cookie.js + * + * Set specified cookie to an arbitrary value. + * + * Reference: + * https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/trusted-set-cookie.js#L23 + * + **/ + +builtinScriptlets.push({ + name: 'trusted-set-cookie.js', + requiresTrust: true, + fn: trustedSetCookie, + world: 'ISOLATED', + dependencies: [ + 'safe-self.fn', + 'set-cookie.fn', + ], +}); +function trustedSetCookie( + name = '', + value = '', + offsetExpiresSec = '', + path = '' +) { + if ( name === '' ) { return; } + + const time = new Date(); + + if ( value === '$now$' ) { + value = Date.now(); + } else if ( value === '$currentDate$' ) { + value = time.toUTCString(); + } + + let expires = ''; + if ( offsetExpiresSec !== '' ) { + if ( offsetExpiresSec === '1day' ) { + time.setDate(time.getDate() + 1); + } else if ( offsetExpiresSec === '1year' ) { + time.setFullYear(time.getFullYear() + 1); + } else { + if ( /^\d+$/.test(offsetExpiresSec) === false ) { return; } + time.setSeconds(time.getSeconds() + parseInt(offsetExpiresSec, 10)); + } + expires = time.toUTCString(); + } + + setCookieFn( + true, + name, + value, + expires, + path, + safeSelf().getExtraArgs(Array.from(arguments), 4) + ); +} + +// For compatibility with AdGuard +builtinScriptlets.push({ + name: 'trusted-set-cookie-reload.js', + requiresTrust: true, + fn: trustedSetCookieReload, + world: 'ISOLATED', + dependencies: [ + 'trusted-set-cookie.js', + ], +}); +function trustedSetCookieReload(name, value, offsetExpiresSec, path, ...args) { + trustedSetCookie(name, value, offsetExpiresSec, path, 'reload', '1', ...args); +} + +/******************************************************************************* + * + * trusted-set-local-storage-item.js + * + * Set a local storage entry to an arbitrary value. + * + * Reference: + * https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/trusted-set-local-storage-item.js + * + **/ + +builtinScriptlets.push({ + name: 'trusted-set-local-storage-item.js', + requiresTrust: true, + fn: trustedSetLocalStorageItem, + world: 'ISOLATED', + dependencies: [ + 'set-local-storage-item.fn', + ], +}); +function trustedSetLocalStorageItem(key = '', value = '') { + setLocalStorageItemFn('local', true, key, value); +} + +builtinScriptlets.push({ + name: 'trusted-set-session-storage-item.js', + requiresTrust: true, + fn: trustedSetSessionStorageItem, + world: 'ISOLATED', + dependencies: [ + 'set-local-storage-item.fn', + ], +}); +function trustedSetSessionStorageItem(key = '', value = '') { + setLocalStorageItemFn('session', true, key, value); +} + +/******************************************************************************* + * + * trusted-replace-fetch-response.js + * + * Replaces response text content of fetch requests if all given parameters + * match. + * + * Reference: + * https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/trusted-replace-fetch-response.js + * + **/ + +builtinScriptlets.push({ + name: 'trusted-replace-fetch-response.js', + requiresTrust: true, + fn: trustedReplaceFetchResponse, + dependencies: [ + 'replace-fetch-response.fn', + ], +}); +function trustedReplaceFetchResponse(...args) { + replaceFetchResponseFn(true, ...args); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'trusted-replace-xhr-response.js', + requiresTrust: true, + fn: trustedReplaceXhrResponse, + dependencies: [ + 'match-object-properties.fn', + 'parse-properties-to-match.fn', + 'safe-self.fn', + 'should-log.fn', + ], +}); +function trustedReplaceXhrResponse( + pattern = '', + replacement = '', + propsToMatch = '' +) { + const safe = safeSelf(); + const xhrInstances = new WeakMap(); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); + const logLevel = shouldLog({ + log: pattern === '' && 'all' || extraArgs.log, + }); + const log = logLevel ? ((...args) => { safe.uboLog(...args); }) : (( ) => { }); + if ( pattern === '*' ) { pattern = '.*'; } + const rePattern = safe.patternToRegex(pattern); + const propNeedles = parsePropertiesToMatch(propsToMatch, 'url'); + self.XMLHttpRequest = class extends self.XMLHttpRequest { + open(method, url, ...args) { + const outerXhr = this; + const xhrDetails = { method, url }; + let outcome = 'match'; + if ( propNeedles.size !== 0 ) { + if ( matchObjectProperties(propNeedles, xhrDetails) === false ) { + outcome = 'nomatch'; + } + } + if ( outcome === logLevel || outcome === 'all' ) { + log(`xhr.open(${method}, ${url}, ${args.join(', ')})`); + } + if ( outcome === 'match' ) { + xhrInstances.set(outerXhr, xhrDetails); + } + return super.open(method, url, ...args); + } + get response() { + const innerResponse = super.response; + const xhrDetails = xhrInstances.get(this); + if ( xhrDetails === undefined ) { + return innerResponse; + } + const responseLength = typeof innerResponse === 'string' + ? innerResponse.length + : undefined; + if ( xhrDetails.lastResponseLength !== responseLength ) { + xhrDetails.response = undefined; + xhrDetails.lastResponseLength = responseLength; + } + if ( xhrDetails.response !== undefined ) { + return xhrDetails.response; + } + if ( typeof innerResponse !== 'string' ) { + return (xhrDetails.response = innerResponse); + } + const textBefore = innerResponse; + const textAfter = textBefore.replace(rePattern, replacement); + const outcome = textAfter !== textBefore ? 'match' : 'nomatch'; + if ( outcome === logLevel || logLevel === 'all' ) { + log( + `trusted-replace-xhr-response (${outcome})`, + `\n\tpattern: ${pattern}`, + `\n\treplacement: ${replacement}`, + ); + } + return (xhrDetails.response = textAfter); + } + get responseText() { + const response = this.response; + if ( typeof response !== 'string' ) { + return super.responseText; + } + return response; + } + }; +} + +/******************************************************************************* + * + * trusted-click-element.js + * + * Reference API: + * https://github.com/AdguardTeam/Scriptlets/blob/master/src/scriptlets/trusted-click-element.js + * + **/ + +builtinScriptlets.push({ + name: 'trusted-click-element.js', + requiresTrust: true, + fn: trustedClickElement, + world: 'ISOLATED', + dependencies: [ + 'run-at-html-element.fn', + 'safe-self.fn', + ], +}); +function trustedClickElement( + selectors = '', + extraMatch = '', // not yet supported + delay = '' +) { + if ( extraMatch !== '' ) { return; } + + const safe = safeSelf(); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); + const uboLog = extraArgs.log !== undefined + ? ((...args) => { safe.uboLog(...args); }) + : (( ) => { }); + + const querySelectorEx = (selector, context = document) => { + const pos = selector.indexOf(' >>> '); + if ( pos === -1 ) { return context.querySelector(selector); } + const outside = selector.slice(0, pos).trim(); + const inside = selector.slice(pos + 5).trim(); + const elem = context.querySelector(outside); + if ( elem === null ) { return null; } + const shadowRoot = elem.shadowRoot; + return shadowRoot && querySelectorEx(inside, shadowRoot); + }; + + const selectorList = selectors.split(/\s*,\s*/) + .filter(s => { + try { + void querySelectorEx(s); + } catch(_) { + return false; + } + return true; + }); + if ( selectorList.length === 0 ) { return; } + + const clickDelay = parseInt(delay, 10) || 1; + const t0 = Date.now(); + const tbye = t0 + 10000; + let tnext = selectorList.length !== 1 ? t0 : t0 + clickDelay; + + const terminate = ( ) => { + selectorList.length = 0; + next.stop(); + observe.stop(); + }; + + const next = notFound => { + if ( selectorList.length === 0 ) { + uboLog(`trusted-click-element: Completed`); + return terminate(); + } + const tnow = Date.now(); + if ( tnow >= tbye ) { + uboLog(`trusted-click-element: Timed out`); + return terminate(); + } + if ( notFound ) { observe(); } + const delay = Math.max(notFound ? tbye - tnow : tnext - tnow, 1); + next.timer = setTimeout(( ) => { + next.timer = undefined; + process(); + }, delay); + uboLog(`trusted-click-element: Waiting for ${selectorList[0]}...`); + }; + next.stop = ( ) => { + if ( next.timer === undefined ) { return; } + clearTimeout(next.timer); + next.timer = undefined; + }; + + const observe = ( ) => { + if ( observe.observer !== undefined ) { return; } + observe.observer = new MutationObserver(( ) => { + if ( observe.timer !== undefined ) { return; } + observe.timer = setTimeout(( ) => { + observe.timer = undefined; + process(); + }, 20); + }); + observe.observer.observe(document, { + attributes: true, + childList: true, + subtree: true, + }); + }; + observe.stop = ( ) => { + if ( observe.timer !== undefined ) { + clearTimeout(observe.timer); + observe.timer = undefined; + } + if ( observe.observer ) { + observe.observer.disconnect(); + observe.observer = undefined; + } + }; + + const process = ( ) => { + next.stop(); + if ( Date.now() < tnext ) { return next(); } + const selector = selectorList.shift(); + if ( selector === undefined ) { return terminate(); } + const elem = querySelectorEx(selector); + if ( elem === null ) { + selectorList.unshift(selector); + return next(true); + } + uboLog(`trusted-click-element: Clicked ${selector}`); + elem.click(); + tnext += clickDelay; + next(); + }; + + runAtHtmlElementFn(process); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'trusted-prune-inbound-object.js', + requiresTrust: true, + fn: trustedPruneInboundObject, + dependencies: [ + 'object-find-owner.fn', + 'object-prune.fn', + 'safe-self.fn', + ], +}); +function trustedPruneInboundObject( + entryPoint = '', + argPos = '', + rawPrunePaths = '', + rawNeedlePaths = '' +) { + if ( entryPoint === '' ) { return; } + let context = globalThis; + let prop = entryPoint; + for (;;) { + const pos = prop.indexOf('.'); + if ( pos === -1 ) { break; } + context = context[prop.slice(0, pos)]; + if ( context instanceof Object === false ) { return; } + prop = prop.slice(pos+1); + } + if ( typeof context[prop] !== 'function' ) { return; } + const argIndex = parseInt(argPos); + if ( isNaN(argIndex) ) { return; } + if ( argIndex < 1 ) { return; } + const safe = safeSelf(); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 4); + const needlePaths = []; + if ( rawPrunePaths !== '' ) { + needlePaths.push(...rawPrunePaths.split(/ +/)); + } + if ( rawNeedlePaths !== '' ) { + needlePaths.push(...rawNeedlePaths.split(/ +/)); + } + const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true }); + const mustProcess = root => { + for ( const needlePath of needlePaths ) { + if ( objectFindOwnerFn(root, needlePath) === false ) { + return false; + } + } + return true; + }; + context[prop] = new Proxy(context[prop], { + apply: function(target, thisArg, args) { + const targetArg = argIndex <= args.length + ? args[argIndex-1] + : undefined; + if ( targetArg instanceof Object && mustProcess(targetArg) ) { + let objBefore = targetArg; + if ( extraArgs.dontOverwrite ) { + try { + objBefore = safe.JSON_parse(safe.JSON_stringify(targetArg)); + } catch(_) { + objBefore = undefined; + } + } + if ( objBefore !== undefined ) { + const objAfter = objectPruneFn( + objBefore, + rawPrunePaths, + rawNeedlePaths, + stackNeedle, + extraArgs + ); + args[argIndex-1] = objAfter || objBefore; + } + } + return Reflect.apply(target, thisArg, args); + }, + }); +} + +/******************************************************************************/ + +builtinScriptlets.push({ + name: 'trusted-prune-outbound-object.js', + requiresTrust: true, + fn: trustedPruneOutboundObject, + dependencies: [ + 'object-prune.fn', + 'safe-self.fn', + ], +}); +function trustedPruneOutboundObject( + entryPoint = '', + rawPrunePaths = '', + rawNeedlePaths = '' +) { + if ( entryPoint === '' ) { return; } + let context = globalThis; + let prop = entryPoint; + for (;;) { + const pos = prop.indexOf('.'); + if ( pos === -1 ) { break; } + context = context[prop.slice(0, pos)]; + if ( context instanceof Object === false ) { return; } + prop = prop.slice(pos+1); + } + if ( typeof context[prop] !== 'function' ) { return; } + const safe = safeSelf(); + const extraArgs = safe.getExtraArgs(Array.from(arguments), 3); + context[prop] = new Proxy(context[prop], { + apply: function(target, thisArg, args) { + const objBefore = Reflect.apply(target, thisArg, args); + if ( objBefore instanceof Object === false ) { return objBefore; } + const objAfter = objectPruneFn( + objBefore, + rawPrunePaths, + rawNeedlePaths, + { matchAll: true }, + extraArgs + ); + return objAfter || objBefore; + }, + }); +} + +/******************************************************************************/ |