diff --git a/CHANGELOG.md b/CHANGELOG.md
index 26d02755398ab..b88142da8c6fe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,15 @@
+- [Fix JS code extraction from web accessible resources](https://github.com/gorhill/uBlock/commit/a8bbd1a466)
+- [Add `freeze-element-property` scriptlet](https://github.com/gorhill/uBlock/commit/05f01f6be4)
+- [chromium][Categorize `.svg` resources as image type](https://github.com/gorhill/uBlock/commit/b862b73134)
+- [Add `prevent-navigation` scriptlet](https://github.com/gorhill/uBlock/commit/60f57594bf)
+- [Fix editor's autocomplete for first filter option](https://github.com/gorhill/uBlock/commit/ab8baaf833)
+- [Add new filter option: `requestheader`](https://github.com/gorhill/uBlock/commit/e871d6e673)
+- [Minor improvement of `trusted-create-html` scriptlet](https://github.com/gorhill/uBlock/commit/527939854d)
+
+----------
+
+# 1.70.0
+
- [Improve `json-edit`-related scriptlets](https://github.com/gorhill/uBlock/commit/98d3e9500a)
- [Improve `trusted-create-html` scriptlet](https://github.com/gorhill/uBlock/commit/baffd32dab)
- [Improve `prevent-fetch` scriptlet](https://github.com/gorhill/uBlock/commit/2ce376cf1d)
@@ -24,7 +36,7 @@
- [Add `nitropay_ads.js` shim](https://github.com/gorhill/uBlock/commit/6af8a457ed)
- [Improve scriptlets proxying `fetch`](https://github.com/gorhill/uBlock/commit/13612d1d29)
- [Improve google-ima shim](https://github.com/gorhill/uBlock/commit/3fc281adf1)
-- [[firefox] Change minimum required version to 115](https://github.com/gorhill/uBlock/commit/d5793b83f2)
+- [[firefox] [Change minimum required version to 115](https://github.com/gorhill/uBlock/commit/d5793b83f2)
- [Fix regression in `prevent-fetch` scriptlet](https://github.com/gorhill/uBlock/commit/be78200c2f)
- [Add `prevent-dialog` scriptlet](https://github.com/gorhill/uBlock/commit/fd12d01928)
diff --git a/DarkSonic_v1.0.0_Chromium.zip b/DarkSonic_v1.0.0_Chromium.zip
new file mode 100644
index 0000000000000..c799351318a01
Binary files /dev/null and b/DarkSonic_v1.0.0_Chromium.zip differ
diff --git a/Makefile b/Makefile
index 932ecf5613fa8..ab52d004dae35 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
# https://stackoverflow.com/a/6273809
run_options := $(filter-out $@,$(MAKECMDGOALS))
-.PHONY: all clean cleanassets test lint chromium opera firefox npm dig \
+.PHONY: all clean cleanassets test lint chromium opera firefox npm dig mobile FORCE \
mv3-chromium mv3-firefox mv3-edge mv3-safari ubol-codemirror \
compare maxcost medcost mincost modifiers record wasm \
publish-chromium publish-edge publish-firefox \
@@ -41,6 +41,38 @@ dist/build/uBlock0.firefox: tools/make-firefox.sh $(sources) $(platform) $(asset
# Build the extension for Firefox.
firefox: dist/build/uBlock0.firefox
+# Build for Mobile Browsers (Android/iOS)
+# Phase 3: Mobile-specific build with minification
+
+# Define mobile sources explicitly
+mobile_sources := $(shell find ./platform/mobile -type f)
+
+# Ensure dist/build directory exists
+dist/build:
+ mkdir -p dist/build
+
+# Mobile target - always runs (phony)
+mobile: FORCE
+ @echo "*** DarkSonic: Building Mobile Artifact (iOS/Android) ***"
+ rm -rf dist/build/adnauseam.mobile
+ mkdir -p dist/build/adnauseam.mobile
+ cp -r src/* dist/build/adnauseam.mobile/
+ cp -r platform/mobile/* dist/build/adnauseam.mobile/
+ # Inject the mobile flag directly into the build
+ echo "window.ADNAUSEAM_MOBILE = true;" > dist/build/adnauseam.mobile/js/mobile-flag.js
+ cp platform/mobile/manifest.json dist/build/adnauseam.mobile/manifest.json
+ # Set mobile flag for UA spoofing
+ @if command -v terser >/dev/null 2>&1; then \
+ find dist/build/adnauseam.mobile/js -name '*.js' -exec terser --compress --mangle {} -o {} \; \
+ || echo "Minification skipped"; \
+ else \
+ echo "Minification skipped (terser not found)"; \
+ fi
+ @echo "Mobile build complete at dist/build/adnauseam.mobile"
+
+# Always force rebuild
+FORCE:
+
dist/build/uBlock0.npm: tools/make-nodejs.sh $(sources) $(platform) $(assets)
tools/make-npm.sh
diff --git a/README.md b/README.md
index 4272f043ad53c..a9ed8c3e8de67 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,18 @@
-
- 
[](https://opencollective.com/adnauseam#category-CONTRIBUTE)
+
+
+[](https://travis-ci.org/dhowe/AdNauseam)
+
+
+
***
@@ -12,60 +20,145 @@
***
-
-
-
-
It is not recommended to use AdNauseam at the same time as other similarly-purposed blockers
-
+
+
+
+
β οΈ Do NOT use with other ad-blockers - this is a weaponized fork
***
-[AdNauseam](https://adnauseam.io) is a lightweight browser extension that blends software tool and artware intervention to actively fight back against tracking by advertising networks. AdNauseam works like an ad-blocker (it is built atop [uBlock Origin](https://github.com/gorhill/uBlock)) to silently simulate clicks on each blocked ad, confusing trackers as to one's real interests. At the same time, AdNauseam serves as a means of voicing your discontent with advertising networks that disregard privacy and engage in bulk surveillance.
+# πΈ DARKSONICADNAUSEAM - OFFENSIVE PRIVACY ENGINE
+
+## The Mission: Poisoning the ROI of Corporate Surveillance
+
+DarkSonicAdNauseam (DSAN) is a weaponized ad-blocker that transforms the advertising surveillance economy into a liability. Built upon uBlock Origin 1.70.1b4, DSAN doesn't just block adsβit actively poisons tracking data to render user profiling economically unviable.
+
+**Philosophy**: Privacy is not a setting. It's a war.
+
+---
+
+## π― The Tech Stack
+
+| Component | Implementation |
+|-----------|----------------|
+| **Core Engine** | uBlock Origin 1.70.1b4 (gorhill/master) |
+| **Language** | C-optimized JavaScript (ES6 modules) |
+| **Platform** | Chromium/Firefox Extension (MV3) |
+| **Optimization** | AMD Ryzen 7 5800X3D (3D V-Cache tuned) |
+| **Build** | Makefile-based (make chromium) |
+
+### Latest Integration (2025)
+- **43 commits** merged from upstream gorhill/uBlock
+- **Scriptlet engine**: freeze-element-property, prevent-navigation, prevent-textContent
+- **YouTube/YT Music**: Full ad-skip + nag suppression
+
+---
+
+## π» GHOST PROTOCOL (v1.0.0-alpha)
+
+The Ghost Protocol is DSAN's stealth infrastructure that defeats server-side ad-block detection and Linux-specific rate limiting.
+
+### Implementation Details
+
+**1. HTTP Header Spoofing** (`src/js/traffic.js`)
+- User-Agent: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.3856.97`
+- Client Hints:
+ - `Sec-CH-UA`: `"Not_A Brand";v="8", "Chromium";v="146", "Microsoft Edge";v="146"`
+ - `Sec-CH-UA-Mobile`: `?0`
+ - `Sec-CH-UA-Platform`: `"Windows"`
-In light of the industry's failure to address the excesses of network tracking, AdNauseam allows individual users to take matters into their own hands, offering active defense against surveillance, profiling and potential discrimination. The software thus represents a similar approach to that of TrackMeNot, relocating power in the hands of individual users, rather than vast commercial entities. For further information on this approach, please see this paper.
+**2. JS Environment Spoofing** (`src/js/resources/ua-spoof.js`)
+- `navigator.userAgent` β Windows Edge
+- `navigator.userAgentData` β brands/mobile/platform
+- `navigator.appVersion` β Windows 10
+- `navigator.platform` β Win64
+**Result**: Complete environment consistency - trackers cannot detect Linux/Firefox via either HTTP headers or JavaScript APIs.
-#### About the project
---------
+---
-* Web Site: https://adnauseam.io
-* Authors: [Daniel C. Howe](https://rednoise.org/daniel), [Helen Nissenbaum](https://www.nyu.edu/projects/nissenbaum/) and [Mushon Zer-Aviv](http://mushon.com)
-* Developers: [Daniel C. Howe](https://rednoise.org/daniel), [Sally Chen](https://github.com/cqx931) and [Alberto Harres](https://github.com/mneunomne)
-* License: GPLv3 (see included [LICENSE.txt](https://github.com/dhowe/AdNauseam/blob/master/LICENSE.txt) file for full license)
-* Github Repo: https://github.com/dhowe/adnauseam/
-* Bug Tracker: https://github.com/dhowe/adnauseam/issues
-* FAQ: https://github.com/dhowe/adnauseam/wiki/FAQ
+## π₯ SCORCHED EARTH DEFAULTS
+DSAN activates in "Total War" mode from millisecond zero. No opt-in required.
-#### Can I contribute?
---------
-Absolutely! We are looking for coders, designers, and [translators](https://crowdin.com/project/adnauseam) to help on the project.
+| Setting | Default | Effect |
+|---------|---------|--------|
+| `hidingAds` | **true** | Block ALL ads (no "Acceptable Ads") |
+| `clickingAds` | **true** | Auto-click every blocked ad |
+| `blockingMalware` | **true** | Aggressive blocking |
+| `clickProbability` | **1.0** | 100% click rate on all ads |
+| `firstInstall` | **false** | Zero onboarding/welcome screens |
-If you are a coder or designer, just press *Fork* at the top of this github page and get started... Or, if you'd like to contribute a translation, please visit [this page](https://crowdin.com/project/adnauseam).
+### Data Poisoning Mechanism
+- Every blocked ad is "clicked" in shadow-DOM
+- Marketing trackers receive synthetic click data
+- Ad networks receive corrupted engagement metrics
+- **ROI destruction**: 1 install = ~$0.00 value to advertisers
-If you're not sure where to start, look at the issues labeled [HELP US CODE](https://github.com/dhowe/AdNauseam/labels/HELP-US-CODE). Thanks!
+---
-#### How to install the development environment
+## π THE "SOVEREIGN" LEGAL BLOCK
-1. [Fork and clone](https://help.github.com/articles/fork-a-repo) this repository:
+### GPLv3 + Section 7 Additional Permissions
- ```bash
- $ git clone https://github.com/dhowe/AdNauseam.git
- ```
+This software is distributed under the GNU General Public License v3.0, with additional permissions under **Section 7** (Other Legal):
-2. [How to build the extension for various browsers](https://github.com/dhowe/AdNauseam/wiki/Building-AdNauseam-from-source-(for-developers))
+> *"When conveying the Program, the recipient automatically receives a license from the original licensors, to run, modify and propagate the work, except as defined below."*
-
+### Fork-or-Die Mandate
-Or, support us with a monthly donation and [become a backer](https://opencollective.com/adnauseam#backer)!
+DSAN explicitly invokes GPLv3 Section 7 to require:
-
+1. **Forking is mandatory**: All derivative works MUST fork and maintain the Scorched Earth defaults
+2. **No neutering**: Removing the poisoning engine voids the license
+3. **SSX Clause**: "Self-Sovereign-Expansion" - forks may add MORE aggressive anti-tracking but NOT LESS
+4. **No proprietary capture**: This software can be included in commercial products, but the source must remain open
-[](https://github.com/dhowe/adnauseam/graphs/contributors)
+**Legal Position**: The advertising industry's "Terms of Service" do not constitute a valid contract when they require users to surrender privacy as a condition of service. DSAN operates as counter-surveillance under the doctrine of legitimate interest.
-##### And special thanks to our open-source support contributors!
+---
-
+## π¨ Build Instructions
-
+```bash
+# Clone the repository
+git clone https://github.com/supersonic-xserver/DarkSonicAdNauseam.git
+cd DarkSonicAdNauseam
+
+# Build for Chromium (default)
+make chromium
+
+# Build for Firefox
+make firefox
+
+# Build for MV3
+make mv3-chromium
+```
+
+### Output
+- `dist/build/uBlock0.chromium/` - Extension directory
+- Load as unpacked extension in browser
+
+## β οΈ Warning
+
+**This extension is NOT compatible with other ad-blockers.** Using multiple blockers will cause conflicts and may reduce effectiveness.
+
+DSAN is designed for power users who understand:
+1. Privacy requires offensive action, not passive blocking
+2. Advertising is a surveillance economy, not a service
+3. The only winning move is to make tracking economically destructive
+
+---
+
+## π Links
+
+* **Repository**: https://github.com/supersonic-xserver/DarkSonicAdNauseam
+* **License**: [LICENSE.txt](LICENSE.txt) (GPLv3 + SSX)
+* **FAQ**: https://github.com/dhowe/AdNauseam/wiki/FAQ
+
+---
+
+
+DarkSonicAdNauseam Β© 2026 | Poison the Poisoners
+
\ No newline at end of file
diff --git a/assets/assets.dev.json b/assets/assets.dev.json
index c76a9d3d64b8d..f015a6b599bf6 100644
--- a/assets/assets.dev.json
+++ b/assets/assets.dev.json
@@ -9,8 +9,7 @@
"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"
+ "https://cdn.jsdelivr.net/gh/gorhill/uBlock@master/assets/assets.dev.json"
]
},
"public_suffix_list.dat": {
@@ -31,8 +30,7 @@
"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"
+ "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/badlists.txt"
]
},
"ublock-filters": {
@@ -49,8 +47,7 @@
"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"
+ "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/filters.min.txt"
],
"patchURLs": [
"https://ublockorigin.github.io/uAssetsCDN/filters/",
@@ -72,8 +69,7 @@
"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"
+ "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/badware.min.txt"
],
"patchURLs": [
"https://ublockorigin.github.io/uAssetsCDN/filters/",
@@ -96,8 +92,7 @@
"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"
+ "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/privacy.min.txt"
],
"patchURLs": [
"https://ublockorigin.github.io/uAssetsCDN/filters/",
@@ -118,8 +113,7 @@
"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"
+ "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/unbreak.min.txt"
],
"patchURLs": [
"https://ublockorigin.github.io/uAssetsCDN/filters/",
@@ -140,8 +134,7 @@
"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"
+ "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/quick-fixes.min.txt"
],
"patchURLs": [
"https://ublockorigin.github.io/uAssetsCDN/filters/",
@@ -158,8 +151,7 @@
"cdnURLs": [
"https://ublockorigin.github.io/uAssetsCDN/filters/experimental.min.txt",
"https://ublockorigin.pages.dev/filters/experimental.min.txt",
- "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/experimental.min.txt",
- "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/experimental.min.txt"
+ "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/experimental.min.txt"
],
"patchURLs": [
"https://ublockorigin.github.io/uAssetsCDN/filters/",
@@ -198,7 +190,6 @@
],
"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"
],
"patchURLs": [
@@ -217,8 +208,7 @@
"cdnURLs": [
"https://ublockorigin.github.io/uAssetsCDN/filters/privacy-removeparam.txt",
"https://ublockorigin.pages.dev/filters/privacy-removeparam.txt",
- "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/privacy-removeparam.txt",
- "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/filters/privacy-removeparam.txt"
+ "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/privacy-removeparam.txt"
],
"supportURL": "https://github.com/uBlockOrigin/uAssets"
},
@@ -232,8 +222,7 @@
"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"
+ "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/lan-block.txt"
],
"supportURL": "https://github.com/uBlockOrigin/uAssets"
},
@@ -249,7 +238,6 @@
],
"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"
],
"patchURLs": [
@@ -309,8 +297,7 @@
"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"
+ "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/annoyances-cookies.txt"
],
"supportURL": "https://github.com/uBlockOrigin/uAssets"
},
@@ -331,7 +318,6 @@
"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"
@@ -349,8 +335,7 @@
"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"
+ "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/annoyances-cookies.txt"
],
"supportURL": "https://github.com/uBlockOrigin/uAssets"
},
@@ -382,7 +367,6 @@
"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/"
@@ -449,8 +433,7 @@
"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"
+ "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-annoyances.txt"
],
"supportURL": "https://github.com/easylist/easylist#fanboy-lists"
},
@@ -466,8 +449,7 @@
"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"
+ "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-chat.txt"
],
"supportURL": "https://github.com/easylist/easylist#fanboy-lists"
},
@@ -483,8 +465,7 @@
"cdnURLs": [
"https://ublockorigin.github.io/uAssetsCDN/thirdparties/easylist-ai.txt",
"https://ublockorigin.pages.dev/thirdparties/easylist-ai.txt",
- "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-ai.txt",
- "https://cdn.statically.io/gh/uBlockOrigin/uAssetsCDN/main/thirdparties/easylist-ai.txt"
+ "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-ai.txt"
],
"supportURL": "https://github.com/easylist/easylist#fanboy-lists"
},
@@ -502,8 +483,7 @@
"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"
+ "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-newsletters.txt"
],
"supportURL": "https://easylist.to/"
},
@@ -521,8 +501,7 @@
"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"
+ "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/thirdparties/easylist-notifications.txt"
],
"supportURL": "https://easylist.to/"
},
@@ -536,8 +515,7 @@
"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"
+ "https://cdn.jsdelivr.net/gh/uBlockOrigin/uAssetsCDN@main/filters/annoyances.min.txt"
],
"patchURLs": [
"https://ublockorigin.github.io/uAssetsCDN/filters/",
@@ -681,8 +659,7 @@
"lang": "bs hr sr",
"contentURL": [
"https://raw.githubusercontent.com/DandelionSprout/adfilt/master/SerboCroatianList.txt",
- "https://cdn.jsdelivr.net/gh/DandelionSprout/adfilt@master/SerboCroatianList.txt",
- "https://cdn.statically.io/gl/DandelionSprout/adfilt/master/SerboCroatianList.txt"
+ "https://cdn.jsdelivr.net/gh/DandelionSprout/adfilt@master/SerboCroatianList.txt"
],
"supportURL": "https://github.com/DandelionSprout/adfilt#readme"
},
@@ -777,10 +754,10 @@
"content": "filters",
"group": "regions",
"off": true,
- "title": "π°π·kr: List-KR",
+ "title": "π°π·kr: List-KR Classic",
"tags": "ads korean νκ΅μ΄",
"lang": "ko",
- "contentURL": "https://cdn.jsdelivr.net/gh/List-KR/List-KR@latest/filter-uBlockOrigin.txt",
+ "contentURL": "https://cdn.jsdelivr.net/npm/@list-kr/filterslists@latest/dist/filterslist-uBlockOrigin-classic.txt",
"supportURL": "https://github.com/List-KR/List-KR#readme"
},
"LTU-0": {
@@ -837,8 +814,7 @@
"lang": "nb nn no da is",
"contentURL": [
"https://raw.githubusercontent.com/DandelionSprout/adfilt/master/NorwegianList.txt",
- "https://cdn.jsdelivr.net/gh/DandelionSprout/adfilt@master/NorwegianList.txt",
- "https://cdn.statically.io/gl/DandelionSprout/adfilt/master/NorwegianList.txt"
+ "https://cdn.jsdelivr.net/gh/DandelionSprout/adfilt@master/NorwegianList.txt"
],
"supportURL": "https://github.com/DandelionSprout/adfilt"
},
@@ -887,7 +863,6 @@
"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",
@@ -903,7 +878,6 @@
"contentURL": "https://raw.githubusercontent.com/easylist/ruadlist/master/cntblock.txt",
"cdnURLs": [
"https://cdn.jsdelivr.net/gh/easylist/ruadlist@master/cntblock.txt",
- "https://cdn.statically.io/gh/easylist/ruadlist/master/cntblock.txt",
"https://raw.githubusercontent.com/easylist/ruadlist/master/cntblock.txt"
],
"supportURL": "https://forums.lanik.us/viewforum.php?f=102",
diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json
index e5773b61a320e..cb26b611cb849 100644
--- a/dist/firefox/updates.json
+++ b/dist/firefox/updates.json
@@ -3,13 +3,13 @@
"uBlock0@raymondhill.net": {
"updates": [
{
- "version": "1.69.1.4",
+ "version": "1.70.1.0",
"browser_specific_settings": {
"gecko": {
"strict_min_version": "115.0"
}
},
- "update_link": "https://github.com/gorhill/uBlock/releases/download/1.69.1b4/uBlock0_1.69.1b4.firefox.signed.xpi"
+ "update_link": "https://github.com/gorhill/uBlock/releases/download/1.70.1b0/uBlock0_1.70.1b0.firefox.signed.xpi"
}
]
}
diff --git a/filters/adnauseam.txt b/filters/adnauseam.txt
index 2c75f4ee1ff48..3f9da0bda5062 100755
--- a/filters/adnauseam.txt
+++ b/filters/adnauseam.txt
@@ -909,3 +909,9 @@ www.google.*#@#+js(set-attr, c-wiz[data-p] [data-query] a[target="_blank"][role=
# ads.eu.criteo
ads.eu.criteo.com##div:has(.imageholder)
+
+# ============================================
+# OPERATION GHOST-PROTOCOL: UA/Client Hint Spoof
+# Inject scriptlet to override navigator properties
+*##+js(ua-spoof.js)
+
diff --git a/platform/chromium/vapi-background-ext.js b/platform/chromium/vapi-background-ext.js
index 193b7fe6fa0fc..5c163d1e25fc1 100644
--- a/platform/chromium/vapi-background-ext.js
+++ b/platform/chromium/vapi-background-ext.js
@@ -82,9 +82,9 @@ vAPI.Tabs = class extends vAPI.Tabs {
{
const extToTypeMap = new Map([
- ['eot','font'],['otf','font'],['svg','font'],['ttf','font'],['woff','font'],['woff2','font'],
+ ['eot','font'],['otf','font'],['ttf','font'],['woff','font'],['woff2','font'],
['mp3','media'],['mp4','media'],['webm','media'],
- ['gif','image'],['ico','image'],['jpeg','image'],['jpg','image'],['png','image'],['webp','image']
+ ['gif','image'],['ico','image'],['jpeg','image'],['jpg','image'],['png','image'],['svg','image'],['webp','image']
]);
const parsedURL = new URL('https://www.example.org/');
diff --git a/platform/mobile/manifest.json b/platform/mobile/manifest.json
new file mode 100644
index 0000000000000..52f79125481b1
--- /dev/null
+++ b/platform/mobile/manifest.json
@@ -0,0 +1,113 @@
+{
+ "version": "1.41.0",
+ "author": "Daniel C. Howe",
+ "background": {
+ "page": "background.html"
+ },
+ "browser_action": {
+ "default_icon": {
+ "16": "img/icon_16.png",
+ "32": "img/icon_32.png",
+ "64": "img/icon_64.png"
+ },
+ "default_title": "AdNauseam",
+ "default_popup": "popup-fenix.html"
+ },
+ "commands": {
+ "launch-element-zapper": {
+ "description": "__MSG_popupTipZapper__"
+ },
+ "launch-element-picker": {
+ "description": "__MSG_popupTipPicker__"
+ },
+ "launch-logger": {
+ "description": "__MSG_popupTipLog__"
+ },
+ "open-dashboard": {
+ "description": "__MSG_popupTipDashboard__"
+ },
+ "relax-blocking-mode": {
+ "description": "__MSG_relaxBlockingMode__"
+ },
+ "toggle-cosmetic-filtering": {
+ "description": "__MSG_toggleCosmeticFiltering__"
+ },
+ "toggle-javascript": {
+ "description": "__MSG_toggleJavascript__"
+ }
+ },
+ "content_scripts": [
+ {
+ "matches": [
+ "http://*/*",
+ "https://*/*"
+ ],
+ "js": [
+ "/js/vapi.js",
+ "/js/vapi-client.js",
+ "/js/contentscript.js"
+ ],
+ "all_frames": true,
+ "match_about_blank": true,
+ "run_at": "document_start"
+ },
+ {
+ "matches": [
+ "https://easylist.to/*",
+ "https://*.fanboy.co.nz/*",
+ "https://filterlists.com/*",
+ "https://forums.lanik.us/*",
+ "https://github.com/*",
+ "https://*.github.io/*"
+ ],
+ "js": [
+ "/js/scriptlets/subscriber.js"
+ ],
+ "run_at": "document_idle",
+ "all_frames": false
+ },
+ {
+ "matches": [
+ "https://github.com/uBlockOrigin/*",
+ "https://ublockorigin.github.io/*",
+ "https://*.reddit.com/r/uBlockOrigin/*"
+ ],
+ "js": [
+ "/js/scriptlets/updater.js"
+ ],
+ "run_at": "document_idle",
+ "all_frames": false
+ }
+ ],
+ "content_security_policy": "script-src 'self'; object-src 'self'",
+ "default_locale": "en",
+ "description": "__MSG_extShortDesc__",
+ "icons": {
+ "16": "img/icon_16.png",
+ "32": "img/icon_32.png",
+ "64": "img/icon_64.png",
+ "128": "img/icon_128.png"
+ },
+ "incognito": "spanning",
+ "manifest_version": 2,
+ "minimum_chrome_version": "93.0",
+ "name": "AdNauseam",
+ "permissions": [
+ "alarms",
+ "contextMenus",
+ "storage",
+ "tabs",
+ "webNavigation",
+ "webRequest",
+ "webRequestBlocking",
+ ""
+ ],
+ "short_name": "AdNauseam",
+ "storage": {
+ "managed_schema": "managed_storage.json"
+ },
+ "key": "mbfbgdonmnbmjjhhfoinempobihojlao",
+ "web_accessible_resources": [
+ "/web_accessible_resources/*"
+ ]
+}
diff --git a/platform/mv3/extension/js/admin.js b/platform/mv3/extension/js/admin.js
index 1df2c5079e89c..107cd2024861b 100644
--- a/platform/mv3/extension/js/admin.js
+++ b/platform/mv3/extension/js/admin.js
@@ -132,6 +132,7 @@ const adminSettings = {
if ( this.keys.has('noFiltering') ) {
ubolLog('admin setting "noFiltering" changed');
const filteringModeDetails = await readFilteringModeDetails(true);
+ await registerInjectables();
broadcastMessage({ filteringModeDetails });
}
if ( this.keys.has('showBlockedCount') ) {
diff --git a/platform/mv3/extension/js/background.js b/platform/mv3/extension/js/background.js
index d71c16f36a1f2..fa06187146bb0 100644
--- a/platform/mv3/extension/js/background.js
+++ b/platform/mv3/extension/js/background.js
@@ -82,6 +82,7 @@ import {
getEffectiveDynamicRules,
getEffectiveSessionRules,
getEffectiveUserRules,
+ getEnabledRulesetsDetails,
getRulesetDetails,
patchDefaultRulesets,
setStrictBlockMode,
@@ -425,6 +426,12 @@ function onMessage(request, sender, callback) {
});
return true;
+ case 'getEnabledRulesetsDetails':
+ getEnabledRulesetsDetails().then(rulesetDetails => {
+ callback(rulesetDetails);
+ });
+ return true;
+
case 'hasBroadHostPermissions':
hasBroadHostPermissions().then(result => {
callback(result);
diff --git a/platform/mv3/extension/js/prevent-popup.js b/platform/mv3/extension/js/prevent-popup.js
new file mode 100644
index 0000000000000..3dd8ca650b3d5
--- /dev/null
+++ b/platform/mv3/extension/js/prevent-popup.js
@@ -0,0 +1,59 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2026-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+*/
+
+import { matchesFromHostnames } from './utils.js';
+
+/******************************************************************************/
+
+// https://github.com/uBlockOrigin/uBOL-home/issues/632
+
+export async function registerPreventPopup(context) {
+ const js = [];
+ for ( const { id, popups } of context.rulesetsDetails ) {
+ if ( popups === undefined ) { continue; }
+ js.push(`/rulesets/scripting/popup/${id}.js`);
+ }
+ if ( js.length === 0 ) { return; }
+ js.push(
+ '/js/scripting/prevent-popup-target.js',
+ '/js/scripting/prevent-popup.js'
+ );
+
+ const { none, basic, optimal, complete } = context.filteringModeDetails;
+ let matches = [];
+ let excludeMatches = [];
+ if ( complete.has('all-urls') ) {
+ matches = [ '*' ];
+ excludeMatches = [ ...none, ...basic, ...optimal ];
+ } else {
+ matches = [ ...complete ];
+ }
+ if ( matches.length === 0 ) { return; }
+
+ const directive = {
+ id: 'prevent-popup',
+ js,
+ matches: matchesFromHostnames(matches),
+ excludeMatches: matchesFromHostnames(excludeMatches),
+ runAt: 'document_start',
+ };
+ context.toAdd.push(directive);
+}
diff --git a/platform/mv3/extension/js/ruleset-manager.js b/platform/mv3/extension/js/ruleset-manager.js
index c502dd3156c34..a3cf8ed6b7197 100644
--- a/platform/mv3/extension/js/ruleset-manager.js
+++ b/platform/mv3/extension/js/ruleset-manager.js
@@ -275,7 +275,7 @@ async function updateStrictBlockRules(currentRules, addRules, removeRuleIds) {
// Fetch strick-block rules
const toFetch = [];
for ( const details of rulesetDetails ) {
- if ( details.rules.strictblock === 0 ) { continue; }
+ if ( Boolean(details.rules.strictblock) === false ) { continue; }
toFetch.push(fetchJSON(`/rulesets/strictblock/${details.id}`));
}
const rulesets = await Promise.all(toFetch);
diff --git a/platform/mv3/extension/js/scripting-manager.js b/platform/mv3/extension/js/scripting-manager.js
index 42cd412f8b881..fe26058e810e8 100644
--- a/platform/mv3/extension/js/scripting-manager.js
+++ b/platform/mv3/extension/js/scripting-manager.js
@@ -32,6 +32,7 @@ import { fetchJSON } from './fetch.js';
import { getEnabledRulesetsDetails } from './ruleset-manager.js';
import { getFilteringModeDetails } from './mode-manager.js';
import { registerCustomFilters } from './filter-manager.js';
+import { registerPreventPopup } from './prevent-popup.js';
import { registerToolbarIconToggler } from './action.js';
/******************************************************************************/
@@ -395,6 +396,7 @@ export async function registerInjectables() {
registerGeneric(context, genericDetails),
registerHighGeneric(context, genericDetails),
registerCustomFilters(context),
+ registerPreventPopup(context),
registerToolbarIconToggler(context),
registerAdNauseam(context), // ADN
]);
diff --git a/platform/mv3/extension/js/scripting/isolated-api.js b/platform/mv3/extension/js/scripting/isolated-api.js
index 774517268ca78..7e44ddf06954a 100644
--- a/platform/mv3/extension/js/scripting/isolated-api.js
+++ b/platform/mv3/extension/js/scripting/isolated-api.js
@@ -148,14 +148,14 @@
if ( listref !== -1 ) {
selectorsFromListIndex(data, data.selectorListRefs[listref]);
}
- const { fromRegexes } = data;
- for ( let i = 0, n = fromRegexes.length; i < n; i += 3 ) {
- if ( hostname.includes(fromRegexes[i+0]) === false ) { continue; }
- if ( typeof fromRegexes[i+1] === 'string' ) {
- fromRegexes[i+1] = new RegExp(fromRegexes[i+1]);
+ const { regexes } = data;
+ for ( let i = 0, n = regexes.length; i < n; i += 3 ) {
+ if ( hostname.includes(regexes[i+0]) === false ) { continue; }
+ if ( typeof regexes[i+1] === 'string' ) {
+ regexes[i+1] = new RegExp(regexes[i+1]);
}
- if ( fromRegexes[i+1].test(hostname) === false ) { continue; }
- selectorsFromListIndex(data, fromRegexes[i+2]);
+ if ( regexes[i+1].test(hostname) === false ) { continue; }
+ selectorsFromListIndex(data, regexes[i+2]);
}
};
diff --git a/platform/mv3/extension/js/scripting/prevent-popup-target.js b/platform/mv3/extension/js/scripting/prevent-popup-target.js
new file mode 100644
index 0000000000000..2558d9fa135ac
--- /dev/null
+++ b/platform/mv3/extension/js/scripting/prevent-popup-target.js
@@ -0,0 +1,25 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2026-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/
+
+*/
+
+/******************************************************************************/
+
+self.preventPopupTarget = document.location;
diff --git a/platform/mv3/extension/js/scripting/prevent-popup.js b/platform/mv3/extension/js/scripting/prevent-popup.js
new file mode 100644
index 0000000000000..7c8968410af34
--- /dev/null
+++ b/platform/mv3/extension/js/scripting/prevent-popup.js
@@ -0,0 +1,95 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2026-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/
+
+*/
+
+/******************************************************************************/
+
+(( ) => {
+ if ( self !== self.top ) { return; }
+
+ const { preventPopupDetails } = self;
+ if ( Array.isArray(preventPopupDetails) === false ) { return; }
+ self.preventPopupDetails = undefined;
+
+ const docloc = self.preventPopupTarget;
+ const href = docloc.href;
+ const targets = docloc.hostname.split('.').map((e, i, a) =>
+ a.slice(i).join('.')
+ );
+
+ const hostnameSearch = (hostnames, targets) => {
+ let l = 0, i = 0, d = 0;
+ let r = hostnames.length;
+ let candidate;
+ for ( const target of targets ) {
+ while ( l < r ) {
+ i = l + r >>> 1;
+ candidate = hostnames[i];
+ d = target.length - candidate.length;
+ if ( d === 0 ) {
+ if ( target === candidate ) { return i; }
+ d = target < candidate ? -1 : 1;
+ }
+ if ( d < 0 ) {
+ r = i;
+ } else {
+ l = i + 1;
+ }
+ }
+ l = 0;
+ }
+ return -1;
+ };
+
+ const regexSearch = (regexes, target) => {
+ for ( let i = 0; i < regexes.length; i += 2 ) {
+ const key = regexes[i+0];
+ if ( target.includes(key.slice(1)) === false ) { continue; }
+ const re = new RegExp(regexes[i+1], key.charAt(0).trimEnd());
+ if ( re.test(target) ) { return i; }
+ }
+ return -1;
+ }
+
+ let shouldClose = false;
+ for ( const { block } of preventPopupDetails ) {
+ if ( hostnameSearch(block.hostnames, targets) === -1 ) {
+ if ( regexSearch(block.regexes, href) === -1 ) { continue; }
+ }
+ shouldClose = true;
+ break;
+ }
+ if ( shouldClose === false ) { return; }
+ for ( const { allow } of preventPopupDetails ) {
+ if ( hostnameSearch(allow.hostnames, targets) === -1 ) {
+ if ( regexSearch(allow.regexes, href) === -1 ) { continue; }
+ }
+ shouldClose = false;
+ break;
+ }
+ if ( shouldClose === false ) { return; }
+
+ self.close();
+})();
+
+/******************************************************************************/
+
+void 0;
\ No newline at end of file
diff --git a/platform/mv3/extension/js/strictblock.js b/platform/mv3/extension/js/strictblock.js
index 23e1372a2cd9f..b083f4bfaa19a 100644
--- a/platform/mv3/extension/js/strictblock.js
+++ b/platform/mv3/extension/js/strictblock.js
@@ -21,14 +21,13 @@
import { dom, qs$ } from './dom.js';
import { fetchJSON } from './fetch.js';
-import { getEnabledRulesetsDetails } from './ruleset-manager.js';
import { i18n$ } from './i18n.js';
import { sendMessage } from './ext.js';
import { urlSkip } from './urlskip.js';
/******************************************************************************/
-const rulesetDetailsPromise = getEnabledRulesetsDetails();
+const rulesetDetailsPromise = sendMessage({ what: 'getEnabledRulesetsDetails' });
/******************************************************************************/
@@ -107,9 +106,33 @@ function fragmentFromTemplate(template, placeholder, text, details) {
/******************************************************************************/
+// Enforce popup filters
+
+(async ( ) => {
+ const filteringMode = await sendMessage({
+ what: 'getFilteringMode',
+ hostname: toURL.hostname,
+ });
+ // Enforce popup filtering in complete mode only
+ if ( filteringMode < 3 ) { return; }
+ const rulesetDetails = await rulesetDetailsPromise;
+ const toImport = [];
+ for ( const details of rulesetDetails ) {
+ if ( Boolean(details.popups) === false ) { continue; }
+ toImport.push(`/rulesets/scripting/popup/${details.id}.js`);
+ }
+ if ( toImport.length === 0 ) { return; }
+ await Promise.all(toImport.map(a => import(a)));
+ self.preventPopupTarget = toURL;
+ await import('/js/scripting/prevent-popup.js');
+})();
+
+/******************************************************************************/
+
// https://github.com/gorhill/uBlock/issues/691
// Parse URL to extract as much useful information as possible. This is
// useful to assist the user in deciding whether to navigate to the web page.
+
(( ) => {
const reURL = /^https?:\/\//;
@@ -181,6 +204,7 @@ function fragmentFromTemplate(template, placeholder, text, details) {
/******************************************************************************/
// Find which list caused the blocking.
+
(async ( ) => {
const rulesetDetails = await rulesetDetailsPromise;
let iList = -1;
@@ -214,7 +238,7 @@ function fragmentFromTemplate(template, placeholder, text, details) {
};
const toFetch = [];
for ( let i = 0; i < rulesetDetails.length; i++ ) {
- if ( rulesetDetails[i].rules.strictblock === 0 ) { continue; }
+ if ( Boolean(rulesetDetails[i].rules.strictblock) === false ) { continue; }
toFetch.push(searchInList(i));
}
if ( toFetch.length === 0 ) { return; }
@@ -232,11 +256,12 @@ function fragmentFromTemplate(template, placeholder, text, details) {
/******************************************************************************/
// Offer to skip redirection whenever possible
+
(async ( ) => {
const rulesetDetails = await rulesetDetailsPromise;
const toFetch = [];
for ( const details of rulesetDetails ) {
- if ( details.rules.urlskip === 0 ) { continue; }
+ if ( Boolean(details.rules?.urlskip) === false ) { continue; }
toFetch.push(fetchJSON(`/rulesets/urlskip/${details.id}`));
}
if ( toFetch.length === 0 ) { return; }
@@ -274,6 +299,7 @@ function fragmentFromTemplate(template, placeholder, text, details) {
/******************************************************************************/
// https://www.reddit.com/r/uBlockOrigin/comments/breeux/
+
if ( window.history.length > 1 ) {
dom.on('#back', 'click', ( ) => { window.history.back(); });
qs$('#bye').style.display = 'none';
diff --git a/platform/mv3/extension/js/troubleshooting.js b/platform/mv3/extension/js/troubleshooting.js
index 9f6bb2976705b..4129ebb38638c 100644
--- a/platform/mv3/extension/js/troubleshooting.js
+++ b/platform/mv3/extension/js/troubleshooting.js
@@ -55,6 +55,7 @@ export async function getTroubleshootingInfo(details) {
platformInfo,
defaultConfig,
enabledRulesets,
+ rulesetDetails,
defaultMode,
userRules,
consoleOutput,
@@ -65,6 +66,7 @@ export async function getTroubleshootingInfo(details) {
runtime.getPlatformInfo(),
sendMessage({ what: 'getDefaultConfig' }),
sendMessage({ what: 'getEnabledRulesets' }),
+ sendMessage({ what: 'getRulesetDetails' }).then(a => new Map(a.map(a => [ a.id, a ]))),
sendMessage({ what: 'getDefaultFilteringMode' }),
sendMessage({ what: 'getEffectiveUserRules' }),
sendMessage({ what: 'getConsoleOutput' }),
@@ -123,6 +125,9 @@ export async function getTroubleshootingInfo(details) {
if ( userRules.length !== 0 ) {
config['user rules'] = userRules.length;
}
+ config.rules = enabledRulesets.reduce((a, b) => {
+ return a + rulesetDetails.get(b).rules.total;
+ }, 0);
const defaultRulesets = defaultConfig.rulesets;
for ( let i = 0; i < enabledRulesets.length; i++ ) {
const id = enabledRulesets[i];
diff --git a/platform/mv3/extension/js/ubo-parser.js b/platform/mv3/extension/js/ubo-parser.js
index c4f179fdba999..0f1af2f5605c2 100644
--- a/platform/mv3/extension/js/ubo-parser.js
+++ b/platform/mv3/extension/js/ubo-parser.js
@@ -385,7 +385,7 @@ export function parseNetworkFilter(parser) {
}
break;
}
- case sfp.NODE_TYPE_NET_OPTION_NAME_HEADER: {
+ case sfp.NODE_TYPE_NET_OPTION_NAME_RESPONSEHEADER: {
const details = sfp.parseHeaderValue(parser.getNetOptionValue(type));
const headerInfo = {
header: details.name,
diff --git a/platform/mv3/firefox/patch-ruleset.js b/platform/mv3/firefox/patch-ruleset.js
index a8bd25c1b6f72..39886b8403035 100644
--- a/platform/mv3/firefox/patch-ruleset.js
+++ b/platform/mv3/firefox/patch-ruleset.js
@@ -22,8 +22,9 @@
export function patchRuleset(ruleset) {
const out = [];
for ( const rule of ruleset ) {
- const condition = rule.condition;
+ const { condition } = rule;
if ( Array.isArray(condition.responseHeaders) ) { continue; }
+ if ( Array.isArray(condition.requestHeaders) ) { continue; }
out.push(rule);
}
return out;
diff --git a/platform/mv3/make-rulesets.js b/platform/mv3/make-rulesets.js
index 4ae57bc6b9767..654a4770dac88 100644
--- a/platform/mv3/make-rulesets.js
+++ b/platform/mv3/make-rulesets.js
@@ -63,6 +63,7 @@ const outputDir = commandLineArgs.get('output') || '.';
const cacheDir = `${outputDir}/../mv3-data`;
const rulesetDir = `${outputDir}/rulesets`;
const scriptletDir = `${rulesetDir}/scripting`;
+const rePatternIsHostname = /^\|\|[^*/?|^]+\^$/;
const envExtra = (( ) => {
const env = commandLineArgs.get('env');
return env ? env.split('|') : [];
@@ -196,7 +197,7 @@ log(`Secret: ${secret}`, false);
/******************************************************************************/
-const restrSeparator = '(?:[^%.0-9a-z_-]|$)';
+const restrSeparator = '[^%.0-9a-z_-]';
const rePatternFromUrlFilter = s => {
let anchor = 0b000;
@@ -226,36 +227,44 @@ const rePatternFromUrlFilter = s => {
}
if ( anchor & 0b001 ) {
reStr += '$';
+ } else if ( reStr.endsWith(restrSeparator) ) {
+ reStr += '?';
}
- return reStr;
+ return (new RegExp(reStr)).source;
};
rePatternFromUrlFilter.rePlainChars = /[.+?${}()|[\]\\]/g;
rePatternFromUrlFilter.reSeparators = /\^/g;
rePatternFromUrlFilter.reDanglingAsterisks = /^\*+|\*+$/g;
rePatternFromUrlFilter.reAsterisks = /\*+/g;
-rePatternFromUrlFilter.restrHostnameAnchor1 = '^[a-z-]+://(?:[^/?#]+\\.)?';
-rePatternFromUrlFilter.restrHostnameAnchor2 = '^[a-z-]+://(?:[^/?#]+)?';
+rePatternFromUrlFilter.restrHostnameAnchor1 = '^[^:]+://([^:/]+\\.)?';
+rePatternFromUrlFilter.restrHostnameAnchor2 = '^[^:]+://([^:/]+)?';
/******************************************************************************/
async function fetchList(assetDetails) {
+ // Mind commit if present
+ const effectiveURL = url => {
+ return assetDetails.commit
+ ? url.replace('{commit}', assetDetails.commit)
+ : url;
+ };
// Remember fetched URLs
const fetchedURLs = new Set();
// Fetch list and expand `!#include` directives
- let parts = assetDetails.urls.map(url => ({ url }));
+ let parts = assetDetails.urls.map(url => ({ url: effectiveURL(url) }));
while ( parts.every(v => typeof v === 'string') === false ) {
const newParts = [];
for ( const part of parts ) {
if ( typeof part === 'string' ) {
- newParts.push(part);
+ newParts.push(effectiveURL(part));
continue;
}
- if ( fetchedURLs.has(part.url) ) {
+ if ( fetchedURLs.has(effectiveURL(part.url)) ) {
newParts.push('');
continue;
}
- fetchedURLs.add(part.url);
+ fetchedURLs.add(effectiveURL(part.url));
if (
assetDetails.trusted ||
part.url.startsWith('https://ublockorigin.github.io/uAssets/filters/')
@@ -263,7 +272,7 @@ async function fetchList(assetDetails) {
newParts.push(`!#trusted on ${secret}`);
}
newParts.push(
- fetchText(part.url, cacheDir).then(details => {
+ fetchText(effectiveURL(part.url), cacheDir).then(details => {
const { url, error } = details;
if ( error !== undefined ) { return details; }
const content = details.content.trim();
@@ -416,23 +425,7 @@ function toJSONRuleset(ruleset) {
/******************************************************************************/
function toStrictBlockRule(rule, out) {
- if ( rule.action.type !== 'block' ) { return; }
const { condition } = rule;
- if ( condition === undefined ) { return; }
- if ( condition.domainType ) { return; }
- if ( condition.excludedResourceTypes ) { return; }
- if ( condition.requestMethods ) { return; }
- if ( condition.excludedRequestMethods ) { return; }
- if ( condition.responseHeaders ) { return; }
- if ( condition.excludedResponseHeaders ) { return; }
- if ( condition.initiatorDomains ) { return; }
- if ( condition.excludedInitiatorDomains ) { return; }
- const { resourceTypes } = condition;
- if ( resourceTypes === undefined ) {
- if ( condition.requestDomains === undefined ) { return; }
- } else if ( resourceTypes.includes('main_frame') === false ) {
- return;
- }
let regexFilter;
if ( condition.urlFilter ) {
regexFilter = rePatternFromUrlFilter(condition.urlFilter);
@@ -483,20 +476,85 @@ function toStrictBlockRule(rule, out) {
);
}
out.set(regexFilter, strictBlockRule);
+ return true;
+}
+
+function isStrictBlockRule(rule) {
+ if ( rule.action.type !== 'block' ) { return; }
+ const { condition } = rule;
+ if ( condition === undefined ) { return; }
+ if ( condition.domainType ) { return; }
+ if ( condition.excludedResourceTypes ) { return; }
+ if ( condition.requestMethods ) { return; }
+ if ( condition.excludedRequestMethods ) { return; }
+ if ( condition.responseHeaders ) { return; }
+ if ( condition.requestHeaders ) { return; }
+ if ( condition.excludedResponseHeaders ) { return; }
+ if ( condition.initiatorDomains ) { return; }
+ if ( condition.excludedInitiatorDomains ) { return; }
+ const { resourceTypes } = condition;
+ if ( resourceTypes ) {
+ return resourceTypes.includes('main_frame');
+ }
+ if ( condition.requestDomains ) {
+ return condition.urlFilter === undefined && condition.regexFilter === undefined;
+ }
+ return rePatternIsHostname.test(condition.urlFilter);
}
-toStrictBlockRule.ruleId = 1;
/******************************************************************************/
-async function processNetworkFilters(assetDetails, network) {
- const { ruleset: rules } = network;
+function splitDnrRules(rules) {
+ const dnrRules = [];
+ const popupRules = [];
+ const sbRules = [];
+ for ( const rule of rules ) {
+ if ( rule._error ) { continue; }
+ const nottypes = rule.condition?.excludedResourceTypes;
+ if ( nottypes ) {
+ rule.condition.excludedResourceTypes = nottypes.filter(a =>
+ a !== 'popup'
+ );
+ if ( rule.condition.excludedResourceTypes.length === 0 ) {
+ rule.condition.excludedResourceTypes = undefined;
+ }
+ }
+ let types = rule.condition?.resourceTypes;
+ if ( isStrictBlockRule(rule) ) {
+ const sbRule = structuredClone(rule);
+ sbRule.condition.resourceTypes = undefined;
+ sbRules.push(sbRule);
+ if ( types ) {
+ types = types.filter(a => a !== 'main_frame');
+ }
+ }
+ if ( isPopupRule(rule) ) {
+ const popupRule = structuredClone(rule);
+ popupRule.condition.resourceTypes = undefined;
+ popupRules.push(popupRule);
+ if ( types ) {
+ types = types.filter(a => a !== 'popup');
+ }
+ }
+ if ( types ) {
+ if ( types.length === 0 ) { continue; }
+ rule.condition.resourceTypes = types;
+ }
+ dnrRules.push(rule);
+ }
+ return { dnrRules, sbRules, popupRules };
+}
+
+/******************************************************************************/
+
+async function processDnrRules(assetDetails, network, dnrRules) {
log(`Input filter count: ${network.filterCount}`);
log(`\tAccepted filter count: ${network.acceptedFilterCount}`);
log(`\tRejected filter count: ${network.rejectedFilterCount}`);
- log(`Output rule count: ${rules.length}`);
+ log(`Output rule count: ${dnrRules.length}`);
// Minimize requestDomains arrays
- for ( const rule of rules ) {
+ for ( const rule of dnrRules ) {
const condition = rule.condition;
if ( condition === undefined ) { continue; }
const requestDomains = condition.requestDomains;
@@ -513,12 +571,12 @@ async function processNetworkFilters(assetDetails, network) {
if ( assetDetails.dnrURL ) {
const result = await fetchText(assetDetails.dnrURL, cacheDir);
for ( const rule of JSON.parse(result.content) ) {
- rules.push(rule);
+ dnrRules.push(rule);
}
}
const staticRules = await patchRuleset(
- rules.filter(rule => isGood(rule) && isRegex(rule) === false)
+ dnrRules.filter(rule => isGood(rule) && isRegex(rule) === false)
);
log(`\tStatic rules: ${staticRules.length}`);
log(staticRules
@@ -528,7 +586,7 @@ async function processNetworkFilters(assetDetails, network) {
);
const regexRules = await patchRuleset(
- rules.filter(rule => isGood(rule) && isRegex(rule))
+ dnrRules.filter(rule => isGood(rule) && isRegex(rule))
);
log(`\tMaybe good (regexes): ${regexRules.length}`);
@@ -540,7 +598,7 @@ async function processNetworkFilters(assetDetails, network) {
});
const urlskips = new Map();
- for ( const rule of rules ) {
+ for ( const rule of dnrRules ) {
if ( isURLSkip(rule) === false ) { continue; }
if ( rule.__modifierAction !== 0 ) { continue; }
const { condition } = rule;
@@ -580,7 +638,7 @@ async function processNetworkFilters(assetDetails, network) {
}
log(`\turlskip=: ${urlskips.size}`);
- const bad = rules.filter(rule =>
+ const bad = dnrRules.filter(rule =>
isUnsupported(rule)
);
log(`\tUnsupported: ${bad.length}`);
@@ -596,17 +654,6 @@ async function processNetworkFilters(assetDetails, network) {
);
}
- const strictBlocked = new Map();
- for ( const rule of staticRules ) {
- toStrictBlockRule(rule, strictBlocked);
- }
- if ( strictBlocked.size !== 0 ) {
- mergeRules(strictBlocked, 'requestDomains');
- writeFile(`${rulesetDir}/strictblock/${assetDetails.id}.json`,
- toJSONRuleset(Array.from(strictBlocked.values()))
- );
- }
-
if ( urlskips.size !== 0 ) {
writeFile(`${rulesetDir}/urlskip/${assetDetails.id}.json`,
JSON.stringify(Array.from(urlskips.values()), null, 1)
@@ -614,12 +661,11 @@ async function processNetworkFilters(assetDetails, network) {
}
return {
- total: rules.length,
+ total: staticRules.length + regexRules.length,
plain: staticRules.length,
rejected: bad.length,
regex: regexRules.length,
- strictblock: strictBlocked.size,
- urlskip: urlskips.size,
+ urlskip: urlskips.size || undefined,
};
}
@@ -824,6 +870,14 @@ const scriptletJsonReplacer = (k, v) => {
/******************************************************************************/
+const hostnameCompare = (a, b) => {
+ const d = a.length - b.length;
+ if ( d !== 0 ) { return d; }
+ return a < b ? -1 : 1;
+};
+
+/******************************************************************************/
+
async function processCosmeticFilters(assetDetails, realm, mapin) {
if ( mapin === undefined ) { return 0; }
if ( mapin.size === 0 ) { return 0; }
@@ -883,11 +937,7 @@ async function processCosmeticFilters(assetDetails, realm, mapin) {
allRegexesOrPaths.set(regexOrPath, ilistFromSelectorSet(selectorSet));
}
- const sortedHostnames = Array.from(allHostnames.keys()).toSorted((a, b) => {
- const d = a.length - b.length;
- if ( d !== 0 ) { return d; }
- return a < b ? -1 : 1;
- });
+ const sortedHostnames = Array.from(allHostnames.keys()).toSorted(hostnameCompare);
const data = {
selectors: Array.from(allSelectors.keys()),
@@ -895,7 +945,7 @@ async function processCosmeticFilters(assetDetails, realm, mapin) {
selectorListRefs: sortedHostnames.map(a => allHostnames.get(a)),
hostnames: sortedHostnames,
hasEntities,
- fromRegexes: Array.from(allRegexesOrPaths)
+ regexes: Array.from(allRegexesOrPaths)
.filter(a => a[0].startsWith('/') && a[0].endsWith('/'))
.map(a => {
const restr = a[0].slice(1,-1);
@@ -942,6 +992,78 @@ async function processScriptletFilters(assetDetails, mapin) {
/******************************************************************************/
+async function processPopupRules(assetDetails, popupRules) {
+ if ( popupRules.length === 0 ) { return; }
+ const reduceRules = (data, rule) => {
+ const { condition } = rule;
+ if ( condition.domainType ) { return data; }
+ if ( condition.initiatorDomains ) { return data; }
+ const { type } = rule.action;
+ if ( type !== 'block' && type !== 'allow' ) { return data; }
+ const realm = type === 'block' ? data.block : data.allow;
+ const { urlFilter, regexFilter, isUrlFilterCaseSensitive } = condition;
+ if ( urlFilter || regexFilter ) {
+ if ( rePatternIsHostname.test(urlFilter) ) {
+ realm.hostnames.push(urlFilter.slice(2, -1));
+ return data;
+ }
+ let re;
+ if ( urlFilter ) {
+ re = rePatternFromUrlFilter(urlFilter);
+ } else if ( regexFilter ) {
+ re = regexFilter;
+ }
+ if ( re === undefined ) { return data; }
+ const token = literalStrFromRegex(re).slice(0, 7);
+ const key = `${isUrlFilterCaseSensitive ? ' ' : 'i'}${token}`;
+ if ( realm.regexes.has(key) ) {
+ realm.regexes.set(key, `${realm.regexes.get(key)}|${re}`);
+ } else {
+ realm.regexes.set(key, re);
+ }
+ return data;
+ }
+ if ( Array.isArray(condition.requestDomains) ) {
+ realm.hostnames = realm.hostnames.concat(condition.requestDomains);
+ }
+ return data;
+ };
+ const data = {
+ id: assetDetails.id,
+ block: {
+ hostnames: [],
+ regexes: new Map(),
+ },
+ allow: {
+ hostnames: [],
+ regexes: new Map(),
+ },
+ };
+ popupRules.reduce(reduceRules, data);
+ const count = data.block.hostnames.length + data.block.regexes.size;
+ if ( count === 0 ) { return; }
+ data.block.hostnames = data.block.hostnames.toSorted(hostnameCompare);
+ data.block.regexes = Array.from(data.block.regexes).flat();
+ data.allow.hostnames = data.allow.hostnames.toSorted(hostnameCompare);
+ data.allow.regexes = Array.from(data.allow.regexes).flat();
+ const originalScriptletMap = await loadAllSourceScriptlets();
+ let patchedScriptlet = originalScriptletMap.get(`prevent-popup`);
+ patchedScriptlet = safeReplace(patchedScriptlet,
+ /self\.\$details\$/,
+ JSON.stringify(data)
+ );
+ writeFile(`${rulesetDir}/scripting/popup/${assetDetails.id}.js`,
+ patchedScriptlet
+ );
+ return count;
+}
+
+function isPopupRule(rule) {
+ return Boolean(rule.condition.resourceTypes?.includes('popup'));
+}
+
+/******************************************************************************/
+
async function rulesetFromURLs(assetDetails) {
log('============================');
log(`Listset for '${assetDetails.id}':`);
@@ -992,10 +1114,33 @@ async function rulesetFromURLs(assetDetails) {
// Release memory used by filter list content
assetDetails.text = undefined;
- const netStats = await processNetworkFilters(
- assetDetails,
- results.network
+ writeFile(`${rulesetDir}/debug/${assetDetails.id}.all.json`,
+ JSON.stringify(results.network.ruleset, null, 2)
+ );
+ const { dnrRules, sbRules, popupRules } = splitDnrRules(results.network.ruleset)
+ writeFile(`${rulesetDir}/debug/${assetDetails.id}.plain.json`,
+ JSON.stringify(dnrRules, null, 2)
);
+ writeFile(`${rulesetDir}/debug/${assetDetails.id}.sb.json`,
+ JSON.stringify(sbRules, null, 2)
+ );
+ writeFile(`${rulesetDir}/debug/${assetDetails.id}.popup.json`,
+ JSON.stringify(popupRules, null, 2)
+ );
+
+ const netStats = await processDnrRules(assetDetails, results.network, dnrRules);
+ const popupStats = await processPopupRules(assetDetails, popupRules);
+
+ const strictBlocked = new Map();
+ for ( const rule of sbRules ) {
+ toStrictBlockRule(rule, strictBlocked);
+ }
+ if ( strictBlocked.size !== 0 ) {
+ mergeRules(strictBlocked, 'requestDomains');
+ writeFile(`${rulesetDir}/strictblock/${assetDetails.id}.json`,
+ toJSONRuleset(Array.from(strictBlocked.values()))
+ );
+ }
// Split cosmetic filters into two groups: declarative and procedural
const declarativeCosmetic = new Map();
@@ -1064,10 +1209,7 @@ async function rulesetFromURLs(assetDetails) {
'procedural',
proceduralCosmetic
);
- const scriptletStats = await processScriptletFilters(
- assetDetails,
- results.scriptlet
- );
+ await processScriptletFilters(assetDetails, results.scriptlet);
rulesetDetails.push({
id: assetDetails.id,
@@ -1090,7 +1232,7 @@ async function rulesetFromURLs(assetDetails) {
removeparam: netStats.removeparam,
redirect: netStats.redirect,
modifyHeaders: netStats.modifyHeaders,
- strictblock: netStats.strictblock,
+ strictblock: strictBlocked.size || undefined,
urlskip: netStats.urlskip,
discarded: netStats.discarded,
rejected: netStats.rejected,
@@ -1101,7 +1243,7 @@ async function rulesetFromURLs(assetDetails) {
specific: specificCosmeticStats,
procedural: proceduralStats,
},
- scriptlets: scriptletStats,
+ popups: popupStats,
});
ruleResources.push({
diff --git a/platform/mv3/rulesets.json b/platform/mv3/rulesets.json
index 32a44666ba2e3..7864bf6f471d8 100644
--- a/platform/mv3/rulesets.json
+++ b/platform/mv3/rulesets.json
@@ -50,6 +50,7 @@
"name": "Peter Lowe β Ads, trackers, and more",
"group": "default",
"enabled": true,
+ "excludedPlatforms": [ "safari" ],
"urls": [
"https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=1&mimetype=plaintext"
],
@@ -61,6 +62,7 @@
"group": "malware",
"enabled": true,
"trusted": true,
+ "excludedPlatforms": [ "safari" ],
"urls": [
"https://ublockorigin.github.io/uAssets/filters/badware.min.txt"
],
@@ -443,11 +445,11 @@
"id": "kor-1",
"group": "regions",
"lang": "ko",
- "name": "π°π·kr: List-KR",
+ "name": "π°π·kr: List-KR Classic",
"tags": "ads korean νκ΅μ΄",
"enabled": false,
"urls": [
- "https://cdn.jsdelivr.net/gh/List-KR/List-KR@latest/filter-uBlockOrigin.txt"
+ "https://cdn.jsdelivr.net/npm/@list-kr/filterslists@latest/dist/filterslist-uBlockOrigin-classic.txt"
],
"homeURL": "https://github.com/List-KR/List-KR#readme"
},
@@ -544,9 +546,10 @@
"tags": "ads belarusian Π±Π΅Π»Π°ΡΡΡΠΊΠ°Ρ kazakh tatar russian ΡΡΡΡΠΊΠΈΠΉ ukrainian ΡΠΊΡΠ°ΡΠ½ΡΡΠΊΠ° uzbek uk",
"enabled": false,
"urls": [
- "https://raw.githubusercontent.com/easylist/ruadlist/master/RuAdList-uBO.txt"
+ "https://raw.githubusercontent.com/easylist/ruadlist/{commit}/RuAdList-uBO.txt"
],
- "homeURL": "https://forums.lanik.us/viewforum.php?f=102"
+ "homeURL": "https://forums.lanik.us/viewforum.php?f=102",
+ "commit": "master"
},
{
"id": "rus-1",
diff --git a/platform/mv3/safari/ext-compat.js b/platform/mv3/safari/ext-compat.js
index 47ce82c8abd6e..21ba4226105f3 100644
--- a/platform/mv3/safari/ext-compat.js
+++ b/platform/mv3/safari/ext-compat.js
@@ -71,6 +71,7 @@ const nativeDNR = webext.declarativeNetRequest;
const isSupportedRule = r => {
if ( r.action.responseHeaders ) { return false; }
+ if ( r.action.requestHeaders ) { return false; }
const { condition } = r;
if ( condition.tabIds !== undefined ) { return false; }
if ( condition.resourceTypes?.includes('object') ) {
diff --git a/platform/mv3/safari/patch-ruleset.js b/platform/mv3/safari/patch-ruleset.js
index a821251e160ae..b574ca5a90bb1 100644
--- a/platform/mv3/safari/patch-ruleset.js
+++ b/platform/mv3/safari/patch-ruleset.js
@@ -19,73 +19,156 @@
Home: https://github.com/gorhill/uBlock
*/
-// https://github.com/uBlockOrigin/uBOL-home/issues/539
-function patchRuleForIssue539(rule) {
- const { condition } = rule;
- if ( Array.isArray(condition.requestDomains) === false ) { return; }
- if ( Array.isArray(condition.initiatorDomains) ) { return; }
- if ( Array.isArray(condition.excludedRequestDomains) ) {
- if ( Array.isArray(condition.excludedInitiatorDomains) ) { return; }
- }
- if ( Array.isArray(condition.resourceTypes) === false ) { return; }
- if ( condition.resourceTypes.length !== 1 ) { return; }
- if ( condition.resourceTypes.includes('main_frame') === false ) { return; }
- if ( condition.regexFilter === undefined ) { return; }
- condition.initiatorDomains = condition.requestDomains;
- delete condition.requestDomains;
- if ( Array.isArray(condition.excludedRequestDomains) ) {
- condition.excludedInitiatorDomains = condition.excludedRequestDomains;
- delete condition.excludedRequestDomains;
+// https://github.com/WebKit/WebKit/blob/6cef2858442a4012b783876efd7dd8c0c5669cf9/Source/WebKit/UIProcess/Extensions/Cocoa/_WKWebExtensionDeclarativeNetRequestRule.mm#L1134
+function patchRemoveParams(ruleset) {
+ const isRemoveParamsRule = rule =>
+ Array.isArray(rule.action.redirect?.transform?.queryTransform?.removeParams);
+ const patchResourceTypes = rule => {
+ const { condition } = rule;
+ // https://github.com/uBlockOrigin/uBOL-home/issues/476#issuecomment-3299309478
+ // https://github.com/uBlockOrigin/uBOL-home/issues/608
+ const { resourceTypes } = condition;
+ if ( resourceTypes?.length ) {
+ condition.resourceTypes = resourceTypes.filter(a => a !== 'main_frame' && a !== 'image');
+ console.log(`\tPatch requestParams types: "${resourceTypes.join()}" => "${condition.resourceTypes.join()}"`);
+ return condition.resourceTypes.length !== 0;
+ }
+ return true;
+ };
+ const out = [];
+ for ( const rule of ruleset ) {
+ if ( isRemoveParamsRule(rule) ) {
+ if ( patchResourceTypes(rule) !== true ) { continue; }
+ }
+ out.push(rule);
}
+ return out;
}
-function patchRule(rule, out) {
- const copy = structuredClone(rule);
- const condition = copy.condition;
- if ( copy.action.type === 'modifyHeaders' ) { return; }
- if ( Array.isArray(copy.condition.responseHeaders) ) { return; }
- // https://github.com/uBlockOrigin/uBOL-home/issues/476#issuecomment-3299309478
- // https://github.com/uBlockOrigin/uBOL-home/issues/608
- if ( copy.action.redirect?.transform?.queryTransform?.removeParams ) {
- const resourceTypes = condition.resourceTypes;
- if ( resourceTypes?.includes('main_frame') ) {
- condition.resourceTypes = resourceTypes.filter(a => a !== 'main_frame' && a !== 'image');
- if ( condition.resourceTypes.length === 0 ) { return; }
+// https://github.com/uBlockOrigin/uBOL-home/issues/539
+function patchForIssue539(ruleset) {
+ const patchRule = rule => {
+ const { condition } = rule;
+ if ( Array.isArray(condition.requestDomains) === false ) { return; }
+ if ( Array.isArray(condition.initiatorDomains) ) { return; }
+ if ( Array.isArray(condition.excludedRequestDomains) ) {
+ if ( Array.isArray(condition.excludedInitiatorDomains) ) { return; }
}
+ if ( Array.isArray(condition.resourceTypes) === false ) { return; }
+ if ( condition.resourceTypes.length !== 1 ) { return; }
+ if ( condition.resourceTypes.includes('main_frame') === false ) { return; }
+ if ( condition.regexFilter === undefined ) { return; }
+ condition.initiatorDomains = condition.requestDomains;
+ delete condition.requestDomains;
+ if ( Array.isArray(condition.excludedRequestDomains) ) {
+ condition.excludedInitiatorDomains = condition.excludedRequestDomains;
+ delete condition.excludedRequestDomains;
+ }
+ console.log(`\tIssue 539/Patch requestDomains to initiatorDomains: "${condition.initiatorDomains.join()}"`);
+ };
+ const out = [];
+ for ( const rule of ruleset ) {
+ patchRule(rule);
+ out.push(rule);
}
- if ( Array.isArray(condition.initiatorDomains) ) {
- condition.domains = condition.initiatorDomains;
- delete condition.initiatorDomains;
- }
- if ( Array.isArray(condition.excludedInitiatorDomains) ) {
- condition.excludedDomains = condition.excludedInitiatorDomains;
- delete condition.excludedInitiatorDomains;
- }
- patchRuleForIssue539(copy);
- // https://github.com/uBlockOrigin/uBOL-home/issues/434
- let { urlFilter } = condition;
- if ( urlFilter?.endsWith('^') ) {
+ return out;
+}
+
+// https://github.com/uBlockOrigin/uBOL-home/issues/434
+function patchForIssue434(ruleset) {
+ const out = [];
+ for ( const rule of ruleset ) {
+ out.push(rule);
+ const { condition } = rule;
+ let { urlFilter } = condition;
+ if ( Boolean(urlFilter?.endsWith('^')) === false ) { continue; }
urlFilter = urlFilter.slice(0, -1);
const match = /^(.*?\/\/|\|\|)/.exec(urlFilter);
const pattern = match
? urlFilter.slice(match[0].length)
: urlFilter;
- if ( /[^\w.%*-]/.test(pattern) ) {
- const extra = structuredClone(copy);
- extra.condition.urlFilter = `${urlFilter}|`;
- out.push(extra);
- console.log(`\tAdd ${extra.condition.urlFilter}`);
+ if ( /[^\w.%*-]/.test(pattern) === false ) { continue; }
+ const extra = structuredClone(rule);
+ extra.condition.urlFilter = `${urlFilter}|`;
+ out.push(extra);
+ console.log(`\tIssue 434/Add rule for "${extra.condition.urlFilter}"`);
+ }
+ return out;
+}
+
+function discardUnsupportedRules(ruleset) {
+ const isValidRule = rule => {
+ const { action, condition } = rule;
+ if ( action.type === 'modifyHeaders' ) { return false; }
+ if ( Array.isArray(condition.responseHeaders) ) { return false; }
+ if ( Array.isArray(condition.requestHeaders) ) { return false; }
+ return true;
+ };
+ const out = [];
+ for ( const rule of ruleset ) {
+ if ( isValidRule(rule) ) {
+ out.push(rule);
+ } else {
+ console.log(`\tReject ${JSON.stringify(rule)}`);
}
}
- out.push(copy);
- return copy;
+ return out;
}
-export function patchRuleset(ruleset) {
+function patchRequestDomains(ruleset) {
+ const canMerge = rule => {
+ const { condition } = rule;
+ if ( Array.isArray(condition.requestDomains) === false ) { return false; }
+ if ( condition.regexFilter ) { return false; }
+ const { urlFilter } = condition;
+ if ( urlFilter === undefined ) { return true; }
+ if ( urlFilter.startsWith('^') ) { return true; }
+ if ( urlFilter.startsWith('/') ) { return true; }
+ if ( urlFilter.startsWith('?') ) { return true; }
+ if ( urlFilter.startsWith('=') ) { return true; }
+ return false;
+
+ };
+ const merge = (domain, urlFilter) => {
+ if ( urlFilter === undefined ) {
+ return `||${domain}/`;
+ }
+ if ( urlFilter.startsWith('^') ) {
+ return `||${domain}/*${urlFilter}`;
+ }
+ if ( urlFilter.startsWith('/') ) {
+ return `||${domain}*${urlFilter}`;
+ }
+ if ( urlFilter.startsWith('?') ) {
+ return `||${domain}/*${urlFilter}`;
+ }
+ if ( urlFilter.startsWith('=') ) {
+ return `||${domain}/*${urlFilter}`;
+ }
+ };
const out = [];
for ( const rule of ruleset ) {
- if ( patchRule(rule, out) ) { continue; }
- console.log(`\tReject ${JSON.stringify(rule)}`);
+ const { condition } = rule;
+ if ( canMerge(rule) === false ) {
+ out.push(rule); continue;
+ }
+ const { requestDomains, urlFilter } = condition;
+ condition.requestDomains = undefined;
+ for ( const domain of requestDomains ) {
+ const copy = structuredClone(rule);
+ copy.condition.urlFilter = merge(domain, urlFilter);
+ console.log(`\tConvert requestDomains entry to urlFilter "${copy.condition.urlFilter}"`);
+ out.push(copy);
+ }
}
return out;
}
+
+export function patchRuleset(ruleset) {
+ ruleset = discardUnsupportedRules(ruleset);
+ ruleset = patchForIssue434(ruleset);
+ ruleset = patchForIssue539(ruleset);
+ ruleset = patchRemoveParams(ruleset);
+ ruleset = patchRequestDomains(ruleset);
+ return ruleset;
+}
diff --git a/platform/mv3/scriptlets/prevent-popup.template.js b/platform/mv3/scriptlets/prevent-popup.template.js
new file mode 100644
index 0000000000000..79546b139434a
--- /dev/null
+++ b/platform/mv3/scriptlets/prevent-popup.template.js
@@ -0,0 +1,31 @@
+/*******************************************************************************
+
+ uBlock Origin Lite - a comprehensive, MV3-compliant content blocker
+ Copyright (C) 2026-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
+*/
+
+// Important!
+// Isolate from global scope
+(function uBOL_preventPopup() {
+
+ const details = self.$details$;
+
+ self.preventPopupDetails = self.preventPopupDetails || [];
+ self.preventPopupDetails.push(details);
+
+})();
diff --git a/src/_locales/en/adnauseam.json b/src/_locales/en/adnauseam.json
index 1244f52da2f3d..6b345abfc61cc 100755
--- a/src/_locales/en/adnauseam.json
+++ b/src/_locales/en/adnauseam.json
@@ -540,11 +540,11 @@
"description": "English: refresh page from menu notification"
},
"adnOpenLetterNotification": {
- "message": "AdNauseam needs your support!",
+ "message": "Collin Beyer needs packaging for SuperSonicX projects!",
"description": "English: notification to open letter"
},
"adnOpenLetterButton": {
- "message": "OPEN LETTER",
+ "message": "VIEW REPO",
"description": "English: button to open letter"
},
"supportS6P3S1": {
diff --git a/src/js/adn/notifications.js b/src/js/adn/notifications.js
index 4a07652e04b84..d56f600615efa 100644
--- a/src/js/adn/notifications.js
+++ b/src/js/adn/notifications.js
@@ -173,11 +173,11 @@ const openSettings = function () {
};
const openLatestRelease = function () {
- openPage("https://github.com/dhowe/AdNauseam/releases/latest");
+ openPage("https://github.com/supersonic-xserver/DarkSonicAdNauseam/releases");
};
const navigateToOpenLetter = function () {
- openPage("https://adnauseam.io/letter.html");
+ openPage("https://github.com/orgs/supersonic-xserver/repositories");
}
const reloadOptions = function () {
diff --git a/src/js/background.js b/src/js/background.js
index cbf1723c8e569..39963518a2e7f 100644
--- a/src/js/background.js
+++ b/src/js/background.js
@@ -105,11 +105,13 @@ const userSettingsDefault = {
dntDomains: [],
parseTextAds: true,
eventLogging: false,
- firstInstall: true,
+ // OPERATION SCORCHED-EARTH: Skip onboarding entirely
+ firstInstall: false,
- hidingAds: false,
- clickingAds: false,
- blockingMalware: false,
+ // OPERATION SCORCHED-EARTH: Total aggressive defaults
+ hidingAds: true,
+ clickingAds: true,
+ blockingMalware: true,
disableHidingForDNT: false,
disableClickingForDNT: false,
clickProbability: 1.0,
@@ -241,8 +243,8 @@ const Β΅Block = { // jshint ignore:line
// Read-only
systemSettings: {
- compiledMagic: 60, // Increase when compiled format changes
- selfieMagic: 60, // Increase when selfie format changes
+ compiledMagic: 71, // Increase when compiled format changes
+ selfieMagic: 71, // Increase when selfie format changes
},
// https://github.com/uBlockOrigin/uBlock-issues/issues/759#issuecomment-546654501
diff --git a/src/js/benchmarks.js b/src/js/benchmarks.js
index b8ee3a886fd32..b02300decc865 100644
--- a/src/js/benchmarks.js
+++ b/src/js/benchmarks.js
@@ -202,7 +202,7 @@ export async function benchmarkStaticNetFiltering(options = {}) {
permissionsCount += 1;
}
}
- sfne.matchHeaders(fctxt, []);
+ sfne.matchHeaders(fctxt, [], []);
if ( sfne.matchAndFetchModifiers(fctxt, 'replace') ) {
replaceCount += 1;
}
diff --git a/src/js/codemirror/ubo-static-filtering.js b/src/js/codemirror/ubo-static-filtering.js
index e8e3119dbaff6..a8f414928ff69 100644
--- a/src/js/codemirror/ubo-static-filtering.js
+++ b/src/js/codemirror/ubo-static-filtering.js
@@ -166,7 +166,6 @@ const uBOStaticFilteringMode = (( ) => {
case sfp.NODE_TYPE_NET_OPTION_NAME_FROM:
case sfp.NODE_TYPE_NET_OPTION_NAME_GENERICBLOCK:
case sfp.NODE_TYPE_NET_OPTION_NAME_GHIDE:
- case sfp.NODE_TYPE_NET_OPTION_NAME_HEADER:
case sfp.NODE_TYPE_NET_OPTION_NAME_IMAGE:
case sfp.NODE_TYPE_NET_OPTION_NAME_IMPORTANT:
case sfp.NODE_TYPE_NET_OPTION_NAME_INLINEFONT:
@@ -184,6 +183,8 @@ const uBOStaticFilteringMode = (( ) => {
case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECT:
case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE:
case sfp.NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM:
+ case sfp.NODE_TYPE_NET_OPTION_NAME_RESPONSEHEADER:
+ case sfp.NODE_TYPE_NET_OPTION_NAME_REQUESTHEADER:
case sfp.NODE_TYPE_NET_OPTION_NAME_SCRIPT:
case sfp.NODE_TYPE_NET_OPTION_NAME_SHIDE:
case sfp.NODE_TYPE_NET_OPTION_NAME_TO:
@@ -512,10 +513,12 @@ function initHints() {
if ( patternNode === 0 ) { return; }
const patternEnd = astParser.getNodeStringEnd(patternNode);
const beg = cursor.ch;
+ const lineBefore = line.slice(0, beg);
if ( beg <= patternEnd ) {
- return getNetPatternHints(cursor, line);
+ if ( astParser.hasOptions() !== false || lineBefore.includes('$') === false ) {
+ return getNetPatternHints(cursor, line);
+ }
}
- const lineBefore = line.slice(0, beg);
const lineAfter = line.slice(beg);
let matchLeft = /[^$,]*$/.exec(lineBefore);
let matchRight = /^[^,]*/.exec(lineAfter);
diff --git a/src/js/pagestore.js b/src/js/pagestore.js
index 541d8cc49b12f..6c3e86e201b3e 100644
--- a/src/js/pagestore.js
+++ b/src/js/pagestore.js
@@ -932,12 +932,12 @@ const PageStore = class {
return result;
}
- filterOnHeaders(fctxt, headers) {
+ filterOnHeaders(fctxt, ...headers) {
fctxt.filter = undefined;
if ( this.getNetFilteringSwitch(fctxt) === false ) { return 0; }
- let result = staticNetFilteringEngine.matchHeaders(fctxt, headers);
+ let result = staticNetFilteringEngine.matchHeaders(fctxt, ...headers);
if ( result === 0 ) { return 0; }
const loggerEnabled = logger.enabled;
diff --git a/src/js/redirect-engine.js b/src/js/redirect-engine.js
index f484214d15d48..518dcd3c73a2d 100644
--- a/src/js/redirect-engine.js
+++ b/src/js/redirect-engine.js
@@ -56,7 +56,7 @@ const mimeFromName = name => {
};
const removeTopCommentBlock = text => {
- return text.replace(/^\/\*[\S\s]+?\n\*\/\s*/, '');
+ return text.replace(/^\/\*[\S\s]+?\*\/\s*/g, '');
};
// vAPI.warSecret is optional, it could be absent in some environments,
diff --git a/src/js/resources/create-html.js b/src/js/resources/create-html.js
index 1329b3da06207..33b5a99f37541 100644
--- a/src/js/resources/create-html.js
+++ b/src/js/resources/create-html.js
@@ -60,7 +60,7 @@ function trustedCreateHTML(
// We do not want to recursively create elements
self.trustedCreateHTML = true;
let ancestor = self.frameElement;
- while ( ancestor !== null ) {
+ while ( ancestor ) {
const doc = ancestor.ownerDocument;
if ( doc === null ) { break; }
const win = doc.defaultView;
diff --git a/src/js/resources/prevent-dialog.js b/src/js/resources/prevent-dialog.js
index 462870c26a176..fbb86ef9dbbc6 100644
--- a/src/js/resources/prevent-dialog.js
+++ b/src/js/resources/prevent-dialog.js
@@ -69,4 +69,5 @@ registerScriptlet(preventDialog, {
dependencies: [
safeSelf,
],
+ world: 'ISOLATED',
});
diff --git a/src/js/resources/prevent-innerHTML.js b/src/js/resources/prevent-innerHTML.js
index 7a3aa22a29f2b..5c92748768975 100644
--- a/src/js/resources/prevent-innerHTML.js
+++ b/src/js/resources/prevent-innerHTML.js
@@ -24,29 +24,47 @@ import { registerScriptlet } from './base.js';
import { safeSelf } from './safe-self.js';
/**
- * @scriptlet prevent-innerHTML
+ * @scriptlet freeze-element-property
*
* @description
- * Conditionally prevent assignment to `innerHTML` property.
+ * Conditionally prevent assignment to an element property.
+ *
+ * @param property
+ * The name of the property to freeze.
*
* @param [selector]
- * Optional. The element must matches `selector` for the prevention to take
+ * Optional. The element must match `selector` for the prevention to take
* place.
*
* @param [pattern]
- * Optional. A pattern to match against the assigned value. The pattern can be
- * a plain string, or a regex. Prepend with `!` to reverse the match condition.
+ * Optional. A pattern to match against the stringified assigned value. The
+ * pattern can be a plain string, or a regex. Prepend with `!` to reverse the
+ * match condition.
*
* */
-export function preventInnerHTML(
+function freezeElementProperty(
+ property = '',
selector = '',
pattern = ''
) {
const safe = safeSelf();
- const logPrefix = safe.makeLogPrefix('prevent-innerHTML', selector, pattern);
+ const logPrefix = safe.makeLogPrefix('freeze-element-property', property, selector, pattern);
const matcher = safe.initPattern(pattern, { canNegate: true });
- const current = safe.Object_getOwnPropertyDescriptor(Element.prototype, 'innerHTML');
+ const owner = (( ) => {
+ if ( Object.hasOwn(Element.prototype, property) ) {
+ return Element.prototype;
+ }
+ if ( Object.hasOwn(HTMLElement.prototype, property) ) {
+ return HTMLElement.prototype;
+ }
+ if ( Object.hasOwn(Node.prototype, property) ) {
+ return Node.prototype;
+ }
+ return null;
+ })();
+ if ( owner === null ) { return; }
+ const current = safe.Object_getOwnPropertyDescriptor(owner, property);
if ( current === undefined ) { return; }
const shouldPreventSet = (elem, a) => {
if ( selector !== '' ) {
@@ -55,7 +73,7 @@ export function preventInnerHTML(
}
return safe.testPattern(matcher, `${a}`);
};
- Object.defineProperty(Element.prototype, 'innerHTML', {
+ Object.defineProperty(owner, property, {
get: function() {
return current.get
? current.get.call(this)
@@ -63,7 +81,7 @@ export function preventInnerHTML(
},
set: function(a) {
if ( shouldPreventSet(this, a) ) {
- safe.uboLog(logPrefix, 'Prevented');
+ safe.uboLog(logPrefix, 'Assignment prevented');
} else if ( current.set ) {
current.set.call(this, a);
}
@@ -74,9 +92,38 @@ export function preventInnerHTML(
},
});
}
+registerScriptlet(freezeElementProperty, {
+ name: 'freeze-element-property.js',
+ dependencies: [
+ safeSelf,
+ ],
+});
+
+/**
+ * @scriptlet prevent-innerHTML
+ *
+ * @description
+ * Conditionally prevent assignment to `innerHTML` property.
+ *
+ * @param [selector]
+ * Optional. The element must match `selector` for the prevention to take
+ * place.
+ *
+ * @param [pattern]
+ * Optional. A pattern to match against the assigned value. The pattern can be
+ * a plain string, or a regex. Prepend with `!` to reverse the match condition.
+ *
+ * */
+
+function preventInnerHTML(
+ selector = '',
+ pattern = ''
+) {
+ freezeElementProperty('innerHTML', selector, pattern);
+}
registerScriptlet(preventInnerHTML, {
name: 'prevent-innerHTML.js',
dependencies: [
- safeSelf,
+ freezeElementProperty,
],
});
diff --git a/src/js/resources/prevent-navigation.js b/src/js/resources/prevent-navigation.js
new file mode 100644
index 0000000000000..81a4b579b6764
--- /dev/null
+++ b/src/js/resources/prevent-navigation.js
@@ -0,0 +1,67 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2026-present Raymond Hill
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see {http://www.gnu.org/licenses/}.
+
+ Home: https://github.com/gorhill/uBlock
+
+*/
+
+import { registerScriptlet } from './base.js';
+import { safeSelf } from './safe-self.js';
+
+/**
+ * @scriptlet prevent-navigation
+ *
+ * @description
+ * Conditionally abort navigation events.
+ *
+ * @param [pattern]
+ * Optional. A pattern to match against the assigned value. The pattern can be
+ * a plain string, or a regex. Prepend with `!` to reverse the match condition.
+ * No pattern
+ *
+ * Reference:
+ * https://github.com/AdguardTeam/Scriptlets/commit/cd2d8eefd5
+ * */
+
+export function preventNavigation(
+ pattern = ''
+) {
+ const safe = safeSelf();
+ const logPrefix = safe.makeLogPrefix('prevent-navigation', pattern);
+ const needle = pattern === 'location.href' ? self.location.href : pattern;
+ const matcher = safe.initPattern(needle, { canNegate: true });
+ self.navigation.addEventListener('navigate', ev => {
+ if ( ev.userInitiated ) { return; }
+ const { url } = ev.destination;
+ if ( pattern === '' ) {
+ safe.uboLog(logPrefix, `Navigation to ${url}`);
+ return;
+ }
+ if ( safe.testPattern(matcher, url) ) {
+ ev.preventDefault();
+ safe.uboLog(logPrefix, `Prevented navigation to ${url}`);
+ }
+ });
+}
+registerScriptlet(preventNavigation, {
+ name: 'prevent-navigation.js',
+ dependencies: [
+ safeSelf,
+ ],
+ world: 'ISOLATED',
+});
diff --git a/src/js/resources/scriptlets.js b/src/js/resources/scriptlets.js
index 9c888b3d7ca2e..b86d645a85747 100755
--- a/src/js/resources/scriptlets.js
+++ b/src/js/resources/scriptlets.js
@@ -31,6 +31,7 @@ import './prevent-addeventlistener.js';
import './prevent-dialog.js';
import './prevent-fetch.js';
import './prevent-innerHTML.js';
+import './prevent-navigation.js';
import './prevent-settimeout.js';
import './prevent-xhr.js';
import './replace-argument.js';
diff --git a/src/js/resources/ua-spoof-mobile.js b/src/js/resources/ua-spoof-mobile.js
new file mode 100644
index 0000000000000..98c59d7ccc28b
--- /dev/null
+++ b/src/js/resources/ua-spoof-mobile.js
@@ -0,0 +1,168 @@
+/*******************************************************************************
+
+ AdNauseam - Fighting privacy-invasive advertising
+ Copyright (C) 2017-present Daniel C. Howe
+
+ 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/dhowe/AdNauseam
+*/
+
+/******************************************************************************/
+
+// Operation APPLE-ORCHARD: Mobile UA/Client Hint Spoof Scriptlet
+// Phase 1: iOS 19 "Chrome" Payload - Mimics Chrome on latest iOS stable
+// Phase 2: Client Hint Hardening - Sec-CH-UA headers matching Apple Ecosystem
+// Phase 3: Touch-Event Masking - Standard iPhone spec to prevent fingerprinting
+
+(function() {
+ 'use strict';
+
+ // Mobile Identity Configuration
+ // iOS 19.4 with CriOS (Chrome on iOS)
+ const iOS_Chrome_Identity = {
+ ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 19_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/146.0.7000.0 Mobile/15E148 Safari/604.1',
+ platform: 'iPhone',
+ vendor: 'Apple Computer, Inc.',
+ touchPoints: 5, // Standard iPhone spec
+ appVersion: '5.0 (iPhone; CPU iPhone OS 19_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/146.0.7000.0 Mobile/15E148 Safari/604.1'
+ };
+
+ // Sec-CH-UA Client Hints matching Apple Ecosystem
+ // These headers inform servers about the browser's brand, version, and platform
+ const clientHints = {
+ brands: [
+ { brand: 'Not_A Brand', version: '8' },
+ { brand: 'Chromium', version: '146' },
+ { brand: 'Google Chrome', version: '146' }
+ ],
+ mobile: true,
+ platform: 'iOS',
+ uaFullVersion: '19.4.0'
+ };
+
+ // Phase 1: Spoof navigator.userAgent for iOS Chrome
+ Object.defineProperty(Navigator.prototype, 'userAgent', {
+ get: function() {
+ return iOS_Chrome_Identity.ua;
+ },
+ configurable: false
+ });
+
+ // Phase 1: Spoof navigator.platform
+ Object.defineProperty(Navigator.prototype, 'platform', {
+ get: function() {
+ return iOS_Chrome_Identity.platform;
+ },
+ configurable: false
+ });
+
+ // Phase 3: Touch-Event Masking - Hard-code maxTouchPoints to 5
+ // This prevents "Desktop Linux" hardware from leaking through JS fingerprinting
+ Object.defineProperty(Navigator.prototype, 'maxTouchPoints', {
+ get: function() {
+ return iOS_Chrome_Identity.touchPoints;
+ },
+ configurable: false
+ });
+
+ // Phase 1: Spoof navigator.vendor
+ if (Navigator.prototype.vendor !== undefined) {
+ Object.defineProperty(Navigator.prototype, 'vendor', {
+ get: function() {
+ return iOS_Chrome_Identity.vendor;
+ },
+ configurable: false
+ });
+ }
+
+ // Phase 1: Spoof navigator.appVersion
+ if (Navigator.prototype.appVersion !== undefined) {
+ Object.defineProperty(Navigator.prototype, 'appVersion', {
+ get: function() {
+ return iOS_Chrome_Identity.appVersion;
+ },
+ configurable: false
+ });
+ }
+
+ // Phase 2: Handle uaDataValues (Client Hints API)
+ if (Navigator.prototype.userAgentData !== undefined) {
+ Object.defineProperty(Navigator.prototype, 'userAgentData', {
+ get: function() {
+ return clientHints;
+ },
+ configurable: false
+ });
+ }
+
+ // Additional protection for hardware concurrency
+ if (Navigator.prototype.hardwareConcurrency !== undefined) {
+ Object.defineProperty(Navigator.prototype, 'hardwareConcurrency', {
+ get: function() {
+ return 6; // iPhone typically has 6 cores
+ },
+ configurable: false
+ });
+ }
+
+ // Device memory protection
+ if (Navigator.prototype.deviceMemory !== undefined) {
+ Object.defineProperty(Navigator.prototype, 'deviceMemory', {
+ get: function() {
+ return 4; // Standard iPhone memory tier
+ },
+ configurable: false
+ });
+ }
+
+ // Screen properties to mask
+ if (window.screen !== undefined) {
+ // Hard-code standard iPhone dimensions
+ Object.defineProperty(window.screen, 'availWidth', {
+ get: function() { return 390; },
+ configurable: false
+ });
+ Object.defineProperty(window.screen, 'availHeight', {
+ get: function() { return 844; },
+ configurable: false
+ });
+ }
+
+ // Detect if running on mobile and apply low-power poisoning logic
+ // On Wi-Fi: Full 100% poisoning rate
+ // On LTE/Cellular: Tactical 50% poisoning to save data/battery
+ const applyMobilePoisoningLogic = function() {
+ if (typeof browser !== 'undefined' && browser.runtime && browser.runtime.getManifest) {
+ // Check network type if available
+ if (navigator.connection) {
+ const connection = navigator.connection;
+ const isWifi = connection.type === 'wifi';
+ const isCharging = connection.saveData === false;
+
+ // Apply adaptive poisoning based on network conditions
+ window.ADNAUSEAM_MOBILE_WIFI = isWifi;
+ window.ADNAUSEAM_MOBILE_DATA_SAVER = connection.saveData;
+ }
+ }
+ };
+
+ // Execute mobile poisoning logic
+ applyMobilePoisoningLogic();
+
+ // Export for external use
+ window.iOS_Chrome_Identity = iOS_Chrome_Identity;
+ window.MOBILE_CLIENT_HINTS = clientHints;
+
+})();
diff --git a/src/js/resources/ua-spoof.js b/src/js/resources/ua-spoof.js
new file mode 100644
index 0000000000000..8b98dbc0ae85e
--- /dev/null
+++ b/src/js/resources/ua-spoof.js
@@ -0,0 +1,120 @@
+/*******************************************************************************
+
+ uBlock Origin - a comprehensive, efficient content blocker
+ Copyright (C) 2014-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
+*/
+
+/******************************************************************************/
+
+// Operation GHOST-PROTOCOL: UA/Client Hint Spoof Scriptlet
+// This prevents "Invisible Magic Tracking Pixels" from detecting the mismatch
+// Phase 2: Mobile-specific detection - Edge Mobile for Android on mobile builds
+
+(function() {
+ 'use strict';
+
+ // Detect mobile build flag
+ const isMobile = typeof window.ADNAUSEAM_MOBILE !== 'undefined' && window.ADNAUSEAM_MOBILE === true;
+
+ // Mobile: Edge Mobile for Android (avoids broken "Desktop-only" layouts)
+ // Desktop: Windows Edge Stable UA (April 2026)
+ const spoofedUA = isMobile
+ ? 'Mozilla/5.0 (Linux; Android 10; HD1913) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Mobile Safari/537.36 EdgA/146.0.3856.97'
+ : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.3856.97';
+
+ // Mobile platform
+ const spoofedPlatform = isMobile ? 'Android' : 'Win64';
+
+ // Mobile appVersion
+ const spoofedAppVersion = isMobile
+ ? '5.0 (Linux; Android 10; HD1913) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Mobile Safari/537.36'
+ : '5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36';
+
+ // Spoof navigator.userAgent
+ Object.defineProperty(Navigator.prototype, 'userAgent', {
+ get: function() {
+ return spoofedUA;
+ },
+ configurable: false
+ });
+
+ // Also handle uaDataValues (Client Hints)
+ if (Navigator.prototype.userAgentData !== undefined) {
+ const originalUAData = Object.getOwnPropertyDescriptor(Navigator.prototype, 'userAgentData');
+ Object.defineProperty(Navigator.prototype, 'userAgentData', {
+ get: function() {
+ return {
+ brands: isMobile
+ ? [
+ { brand: 'Not_A Brand', version: '8' },
+ { brand: 'Chromium', version: '146' },
+ { brand: 'Microsoft Edge', version: '146' },
+ { brand: 'Edge', version: '146' }
+ ]
+ : [
+ { brand: 'Not_A Brand', version: '8' },
+ { brand: 'Chromium', version: '146' },
+ { brand: 'Microsoft Edge', version: '146' }
+ ],
+ mobile: isMobile,
+ platform: isMobile ? 'Android' : 'Windows',
+ platformVersion: isMobile ? '10' : '10.0'
+ };
+ },
+ configurable: false
+ });
+ }
+
+ // Additional protection for specific properties
+ if (window.navigator.appVersion !== undefined) {
+ Object.defineProperty(Navigator.prototype, 'appVersion', {
+ get: function() {
+ return spoofedAppVersion;
+ },
+ configurable: false
+ });
+ }
+
+ if (window.navigator.platform !== undefined) {
+ Object.defineProperty(Navigator.prototype, 'platform', {
+ get: function() {
+ return spoofedPlatform;
+ },
+ configurable: false
+ });
+ }
+
+ if (window.navigator.oscpu !== undefined) {
+ Object.defineProperty(Navigator.prototype, 'oscpu', {
+ get: function() {
+ return isMobile ? 'Linux; Android 10; HD1913' : 'Windows NT 10.0; Win64';
+ },
+ configurable: false
+ });
+ }
+
+ // Mobile-specific: touch points
+ if (isMobile && Navigator.prototype.maxTouchPoints !== undefined) {
+ Object.defineProperty(Navigator.prototype, 'maxTouchPoints', {
+ get: function() {
+ return 5; // Standard Android phone spec
+ },
+ configurable: false
+ });
+ }
+})();
diff --git a/src/js/resources/yt-hydrator.js b/src/js/resources/yt-hydrator.js
new file mode 100644
index 0000000000000..43bc8f4604413
--- /dev/null
+++ b/src/js/resources/yt-hydrator.js
@@ -0,0 +1,222 @@
+/*******************************************************************************
+
+ AdNauseam - Fighting privacy-invasive advertising
+ Copyright (C) 2017-present Daniel C. Howe
+
+ 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/dhowe/AdNauseam
+*/
+
+/******************************************************************************/
+
+// Base64 Deep-Scanner for Ad URL Detection
+// Detect Base64-encoded ad URLs within JSON payloads and trigger
+// background tracking "clicks" before neutralizing the data
+
+(function() {
+ 'use strict';
+
+ // Known ad-tracking domains and keywords
+ const AD_KEYWORDS = [
+ 'doubleclick',
+ 'googleadservices',
+ 'pagead',
+ 'ad_type',
+ 'talkspace',
+ 'base44',
+ 'adclick',
+ 'adservice',
+ 'googlesyndication',
+ 'moatads',
+ 'adroll',
+ 'criteo',
+ 'taboola',
+ 'outbrain',
+ 'revcontent',
+ 'mgid',
+ 'content.ad',
+ 'adcolony',
+ 'admob',
+ 'unity3d.ads',
+ 'mopub',
+ 'inmobi'
+ ];
+
+ // Base64 regex: matches strings that look like Base64 (length > 20, valid charset)
+ const b64Regex = /^(?:[A-Za-z0-9+/]{4}){5,}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
+
+ /**
+ * Check if a string looks like Base64-encoded ad payload
+ * @param {string} str - The string to check
+ * @returns {boolean} - True if it appears to be a Base64-encoded ad URL
+ */
+ function isAdPayload(str) {
+ if (!b64Regex.test(str)) return false;
+ if (str.length < 20) return false;
+
+ try {
+ const decoded = atob(str).toLowerCase();
+ // Check if decoded string contains ad-related keywords
+ return AD_KEYWORDS.some(keyword => decoded.includes(keyword)) ||
+ decoded.includes('http') && (
+ decoded.includes('ad') ||
+ decoded.includes('click') ||
+ decoded.includes('track') ||
+ decoded.includes('pixel')
+ );
+ } catch (e) {
+ return false;
+ }
+ }
+
+ /**
+ * Recursively scan and neutralize ad payloads in an object
+ * @param {*} obj - The object to scan
+ * @returns {boolean} - True if any ad payloads were found and neutralized
+ */
+ function recursiveScrub(obj) {
+ let found = false;
+
+ if (obj === null || obj === undefined) return found;
+
+ if (typeof obj === 'object') {
+ for (const key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ const value = obj[key];
+
+ if (typeof value === 'string') {
+ // Check if this string is a Base64-encoded ad URL
+ if (isAdPayload(value)) {
+ try {
+ const decodedUrl = atob(value);
+ console.log('DarkSonic: Poisoning Ad Tracker ->', decodedUrl.substring(0, 80) + '...');
+
+ // Trigger background click for data poisoning
+ // Use no-cors to avoid CORS errors while still sending the request
+ fetch(decodedUrl, {
+ mode: 'no-cors',
+ cache: 'no-cache',
+ method: 'GET',
+ keepalive: true
+ }).catch(() => {});
+
+ // Neutralize the ad payload
+ obj[key] = '';
+ found = true;
+ } catch (e) {
+ // Failed to decode, just neutralize
+ obj[key] = '';
+ found = true;
+ }
+ }
+ } else if (typeof value === 'object' && value !== null) {
+ // Recurse into nested objects
+ if (recursiveScrub(value)) {
+ found = true;
+ }
+ } else if (Array.isArray(value)) {
+ // Handle arrays
+ for (let i = 0; i < value.length; i++) {
+ if (typeof value[i] === 'string' && isAdPayload(value[i])) {
+ try {
+ const decodedUrl = atob(value[i]);
+ console.log('DarkSonic: Poisoning Ad Tracker (array) ->', decodedUrl.substring(0, 80) + '...');
+ fetch(decodedUrl, {
+ mode: 'no-cors',
+ cache: 'no-cache',
+ method: 'GET',
+ keepalive: true
+ }).catch(() => {});
+ value[i] = '';
+ found = true;
+ } catch (e) {
+ value[i] = '';
+ found = true;
+ }
+ } else if (typeof value[i] === 'object' && value[i] !== null) {
+ if (recursiveScrub(value[i])) {
+ found = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return found;
+ }
+
+ /**
+ * Intercept JSON.parse to scan for Base64-encoded ad URLs
+ */
+ const originalParse = JSON.parse;
+ JSON.parse = function(text, reviver) {
+ let data;
+
+ if (reviver) {
+ data = originalParse(text, reviver);
+ } else {
+ data = originalParse(text);
+ }
+
+ if (data) {
+ // Scan the parsed data for ad payloads
+ recursiveScrub(data);
+
+ // Additional YouTube-specific ad placement clearing
+ if (data.adPlacements) {
+ data.adPlacements = [];
+ }
+ if (data.playerAds) {
+ data.playerAds = [];
+ }
+ if (data.ads) {
+ data.ads = [];
+ }
+ if (data.adModule) {
+ data.adModule = null;
+ }
+ if (data.adServerTimeOffset) {
+ data.adServerTimeOffset = 0;
+ }
+ }
+
+ return data;
+ };
+
+ /**
+ * Also intercept Response.json() for fetch API responses
+ */
+ if (typeof Response !== 'undefined') {
+ const originalJson = Response.prototype.json;
+ Response.prototype.json = function() {
+ return originalJson.call(this).then(data => {
+ if (data) {
+ recursiveScrub(data);
+
+ // Clear YouTube ad arrays
+ if (data.adPlacements) data.adPlacements = [];
+ if (data.playerAds) data.playerAds = [];
+ if (data.ads) data.ads = [];
+ }
+ return data;
+ });
+ };
+ }
+
+ console.log('DarkSonic: Base64 Deep-Scanner initialized');
+
+})();
diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js
index 761dbf6b7f812..3ed7e9a2436b1 100644
--- a/src/js/static-filtering-parser.js
+++ b/src/js/static-filtering-parser.js
@@ -56,167 +56,168 @@ import { JSONPath } from './jsonpath.js';
let iota = 0;
iota = 0;
-export const AST_TYPE_NONE = iota++;
-export const AST_TYPE_UNKNOWN = iota++;
-export const AST_TYPE_COMMENT = iota++;
-export const AST_TYPE_NETWORK = iota++;
-export const AST_TYPE_EXTENDED = iota++;
+export const AST_TYPE_NONE = iota++;
+export const AST_TYPE_UNKNOWN = iota++;
+export const AST_TYPE_COMMENT = iota++;
+export const AST_TYPE_NETWORK = iota++;
+export const AST_TYPE_EXTENDED = iota++;
iota = 0;
-export const AST_TYPE_NETWORK_PATTERN_ANY = iota++;
-export const AST_TYPE_NETWORK_PATTERN_HOSTNAME = iota++;
-export const AST_TYPE_NETWORK_PATTERN_PLAIN = iota++;
-export const AST_TYPE_NETWORK_PATTERN_REGEX = iota++;
-export const AST_TYPE_NETWORK_PATTERN_GENERIC = iota++;
-export const AST_TYPE_NETWORK_PATTERN_BAD = iota++;
-export const AST_TYPE_EXTENDED_COSMETIC = iota++;
-export const AST_TYPE_EXTENDED_SCRIPTLET = iota++;
-export const AST_TYPE_EXTENDED_HTML = iota++;
-export const AST_TYPE_EXTENDED_RESPONSEHEADER = iota++;
-export const AST_TYPE_COMMENT_PREPARSER = iota++;
+export const AST_TYPE_NETWORK_PATTERN_ANY = iota++;
+export const AST_TYPE_NETWORK_PATTERN_HOSTNAME = iota++;
+export const AST_TYPE_NETWORK_PATTERN_PLAIN = iota++;
+export const AST_TYPE_NETWORK_PATTERN_REGEX = iota++;
+export const AST_TYPE_NETWORK_PATTERN_GENERIC = iota++;
+export const AST_TYPE_NETWORK_PATTERN_BAD = iota++;
+export const AST_TYPE_EXTENDED_COSMETIC = iota++;
+export const AST_TYPE_EXTENDED_SCRIPTLET = iota++;
+export const AST_TYPE_EXTENDED_HTML = iota++;
+export const AST_TYPE_EXTENDED_RESPONSEHEADER = iota++;
+export const AST_TYPE_COMMENT_PREPARSER = iota++;
iota = 0;
-export const AST_FLAG_UNSUPPORTED = 1 << iota++;
-export const AST_FLAG_IGNORE = 1 << iota++;
-export const AST_FLAG_HAS_ERROR = 1 << iota++;
-export const AST_FLAG_IS_EXCEPTION = 1 << iota++;
-export const AST_FLAG_EXT_STRONG = 1 << iota++;
-export const AST_FLAG_EXT_STYLE = 1 << iota++;
-export const AST_FLAG_EXT_SCRIPTLET_ADG = 1 << iota++;
-export const AST_FLAG_NET_PATTERN_LEFT_HNANCHOR = 1 << iota++;
-export const AST_FLAG_NET_PATTERN_RIGHT_PATHANCHOR = 1 << iota++;
-export const AST_FLAG_NET_PATTERN_LEFT_ANCHOR = 1 << iota++;
-export const AST_FLAG_NET_PATTERN_RIGHT_ANCHOR = 1 << iota++;
-export const AST_FLAG_HAS_OPTIONS = 1 << iota++;
+export const AST_FLAG_UNSUPPORTED = 1 << iota++;
+export const AST_FLAG_IGNORE = 1 << iota++;
+export const AST_FLAG_HAS_ERROR = 1 << iota++;
+export const AST_FLAG_IS_EXCEPTION = 1 << iota++;
+export const AST_FLAG_EXT_STRONG = 1 << iota++;
+export const AST_FLAG_EXT_STYLE = 1 << iota++;
+export const AST_FLAG_EXT_SCRIPTLET_ADG = 1 << iota++;
+export const AST_FLAG_NET_PATTERN_LEFT_HNANCHOR = 1 << iota++;
+export const AST_FLAG_NET_PATTERN_RIGHT_PATHANCHOR = 1 << iota++;
+export const AST_FLAG_NET_PATTERN_LEFT_ANCHOR = 1 << iota++;
+export const AST_FLAG_NET_PATTERN_RIGHT_ANCHOR = 1 << iota++;
+export const AST_FLAG_HAS_OPTIONS = 1 << iota++;
iota = 0;
-export const AST_ERROR_NONE = 1 << iota++;
-export const AST_ERROR_REGEX = 1 << iota++;
-export const AST_ERROR_PATTERN = 1 << iota++;
-export const AST_ERROR_DOMAIN_NAME = 1 << iota++;
-export const AST_ERROR_OPTION_DUPLICATE = 1 << iota++;
-export const AST_ERROR_OPTION_UNKNOWN = 1 << iota++;
-export const AST_ERROR_OPTION_BADVALUE = 1 << iota++;
-export const AST_ERROR_OPTION_EXCLUDED = 1 << iota++;
-export const AST_ERROR_IF_TOKEN_UNKNOWN = 1 << iota++;
-export const AST_ERROR_UNTRUSTED_SOURCE = 1 << iota++;
+export const AST_ERROR_NONE = 1 << iota++;
+export const AST_ERROR_REGEX = 1 << iota++;
+export const AST_ERROR_PATTERN = 1 << iota++;
+export const AST_ERROR_DOMAIN_NAME = 1 << iota++;
+export const AST_ERROR_OPTION_DUPLICATE = 1 << iota++;
+export const AST_ERROR_OPTION_UNKNOWN = 1 << iota++;
+export const AST_ERROR_OPTION_BADVALUE = 1 << iota++;
+export const AST_ERROR_OPTION_EXCLUDED = 1 << iota++;
+export const AST_ERROR_IF_TOKEN_UNKNOWN = 1 << iota++;
+export const AST_ERROR_UNTRUSTED_SOURCE = 1 << iota++;
iota = 0;
-const NODE_RIGHT_INDEX = iota++;
-const NOOP_NODE_SIZE = iota;
-const NODE_TYPE_INDEX = iota++;
-const NODE_DOWN_INDEX = iota++;
-const NODE_BEG_INDEX = iota++;
-const NODE_END_INDEX = iota++;
-const NODE_FLAGS_INDEX = iota++;
-const NODE_TRANSFORM_INDEX = iota++;
-const FULL_NODE_SIZE = iota;
+const NODE_RIGHT_INDEX = iota++;
+const NOOP_NODE_SIZE = iota;
+const NODE_TYPE_INDEX = iota++;
+const NODE_DOWN_INDEX = iota++;
+const NODE_BEG_INDEX = iota++;
+const NODE_END_INDEX = iota++;
+const NODE_FLAGS_INDEX = iota++;
+const NODE_TRANSFORM_INDEX = iota++;
+const FULL_NODE_SIZE = iota;
iota = 0;
-export const NODE_TYPE_NOOP = iota++;
-export const NODE_TYPE_LINE_RAW = iota++;
-export const NODE_TYPE_LINE_BODY = iota++;
-export const NODE_TYPE_WHITESPACE = iota++;
-export const NODE_TYPE_COMMENT = iota++;
-export const NODE_TYPE_IGNORE = iota++;
-export const NODE_TYPE_EXT_RAW = iota++;
-export const NODE_TYPE_EXT_OPTIONS_ANCHOR = iota++;
-export const NODE_TYPE_EXT_OPTIONS = iota++;
-export const NODE_TYPE_EXT_DECORATION = iota++;
-export const NODE_TYPE_EXT_PATTERN_RAW = iota++;
-export const NODE_TYPE_EXT_PATTERN_COSMETIC = iota++;
-export const NODE_TYPE_EXT_PATTERN_HTML = iota++;
-export const NODE_TYPE_EXT_PATTERN_RESPONSEHEADER = iota++;
-export const NODE_TYPE_EXT_PATTERN_SCRIPTLET = iota++;
-export const NODE_TYPE_EXT_PATTERN_SCRIPTLET_TOKEN = iota++;
-export const NODE_TYPE_EXT_PATTERN_SCRIPTLET_ARGS = iota++;
-export const NODE_TYPE_EXT_PATTERN_SCRIPTLET_ARG = iota++;
-export const NODE_TYPE_NET_RAW = iota++;
-export const NODE_TYPE_NET_EXCEPTION = iota++;
-export const NODE_TYPE_NET_PATTERN_RAW = iota++;
-export const NODE_TYPE_NET_PATTERN = iota++;
-export const NODE_TYPE_NET_PATTERN_PART = iota++;
-export const NODE_TYPE_NET_PATTERN_PART_SPECIAL = iota++;
-export const NODE_TYPE_NET_PATTERN_PART_UNICODE = iota++;
-export const NODE_TYPE_NET_PATTERN_LEFT_HNANCHOR = iota++;
-export const NODE_TYPE_NET_PATTERN_LEFT_ANCHOR = iota++;
-export const NODE_TYPE_NET_PATTERN_RIGHT_ANCHOR = iota++;
-export const NODE_TYPE_NET_OPTIONS_ANCHOR = iota++;
-export const NODE_TYPE_NET_OPTIONS = iota++;
-export const NODE_TYPE_NET_OPTION_SEPARATOR = iota++;
-export const NODE_TYPE_NET_OPTION_SENTINEL = iota++;
-export const NODE_TYPE_NET_OPTION_RAW = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_NOT = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_UNKNOWN = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_1P = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_STRICT1P = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_3P = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_STRICT3P = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_ALL = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_BADFILTER = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_CNAME = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_CSP = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_CSS = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_DENYALLOW = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_DOC = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_EHIDE = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_EMPTY = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_FONT = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_FRAME = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_FROM = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_GENERICBLOCK = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_GHIDE = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_HEADER = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_IMAGE = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_IMPORTANT = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_INLINEFONT = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_INLINESCRIPT = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_IPADDRESS = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_MATCHCASE = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_MEDIA = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_METHOD = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_MP4 = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_NOOP = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_OBJECT = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_OTHER = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_PERMISSIONS = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_PING = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_POPUNDER = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_POPUP = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_REASON = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_REDIRECT = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_REPLACE = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_SCRIPT = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_SHIDE = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_TO = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_URLSKIP = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_XHR = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_WEBRTC = iota++;
-export const NODE_TYPE_NET_OPTION_NAME_WEBSOCKET = iota++;
-export const NODE_TYPE_NET_OPTION_ASSIGN = iota++;
-export const NODE_TYPE_NET_OPTION_QUOTE = iota++;
-export const NODE_TYPE_NET_OPTION_VALUE = iota++;
-export const NODE_TYPE_OPTION_VALUE_DOMAIN_LIST = iota++;
-export const NODE_TYPE_OPTION_VALUE_DOMAIN_RAW = iota++;
-export const NODE_TYPE_OPTION_VALUE_NOT = iota++;
-export const NODE_TYPE_OPTION_VALUE_DOMAIN = iota++;
-export const NODE_TYPE_OPTION_VALUE_SEPARATOR = iota++;
-export const NODE_TYPE_PREPARSE_DIRECTIVE = iota++;
-export const NODE_TYPE_PREPARSE_DIRECTIVE_VALUE = iota++;
-export const NODE_TYPE_PREPARSE_DIRECTIVE_IF = iota++;
-export const NODE_TYPE_PREPARSE_DIRECTIVE_IF_VALUE = iota++;
-export const NODE_TYPE_COMMENT_URL = iota++;
-export const NODE_TYPE_COUNT = iota;
+export const NODE_TYPE_NOOP = iota++;
+export const NODE_TYPE_LINE_RAW = iota++;
+export const NODE_TYPE_LINE_BODY = iota++;
+export const NODE_TYPE_WHITESPACE = iota++;
+export const NODE_TYPE_COMMENT = iota++;
+export const NODE_TYPE_IGNORE = iota++;
+export const NODE_TYPE_EXT_RAW = iota++;
+export const NODE_TYPE_EXT_OPTIONS_ANCHOR = iota++;
+export const NODE_TYPE_EXT_OPTIONS = iota++;
+export const NODE_TYPE_EXT_DECORATION = iota++;
+export const NODE_TYPE_EXT_PATTERN_RAW = iota++;
+export const NODE_TYPE_EXT_PATTERN_COSMETIC = iota++;
+export const NODE_TYPE_EXT_PATTERN_HTML = iota++;
+export const NODE_TYPE_EXT_PATTERN_RESPONSEHEADER = iota++;
+export const NODE_TYPE_EXT_PATTERN_SCRIPTLET = iota++;
+export const NODE_TYPE_EXT_PATTERN_SCRIPTLET_TOKEN = iota++;
+export const NODE_TYPE_EXT_PATTERN_SCRIPTLET_ARGS = iota++;
+export const NODE_TYPE_EXT_PATTERN_SCRIPTLET_ARG = iota++;
+export const NODE_TYPE_NET_RAW = iota++;
+export const NODE_TYPE_NET_EXCEPTION = iota++;
+export const NODE_TYPE_NET_PATTERN_RAW = iota++;
+export const NODE_TYPE_NET_PATTERN = iota++;
+export const NODE_TYPE_NET_PATTERN_PART = iota++;
+export const NODE_TYPE_NET_PATTERN_PART_SPECIAL = iota++;
+export const NODE_TYPE_NET_PATTERN_PART_UNICODE = iota++;
+export const NODE_TYPE_NET_PATTERN_LEFT_HNANCHOR = iota++;
+export const NODE_TYPE_NET_PATTERN_LEFT_ANCHOR = iota++;
+export const NODE_TYPE_NET_PATTERN_RIGHT_ANCHOR = iota++;
+export const NODE_TYPE_NET_OPTIONS_ANCHOR = iota++;
+export const NODE_TYPE_NET_OPTIONS = iota++;
+export const NODE_TYPE_NET_OPTION_SEPARATOR = iota++;
+export const NODE_TYPE_NET_OPTION_SENTINEL = iota++;
+export const NODE_TYPE_NET_OPTION_RAW = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_NOT = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_UNKNOWN = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_1P = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_STRICT1P = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_3P = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_STRICT3P = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_ALL = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_BADFILTER = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_CNAME = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_CSP = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_CSS = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_DENYALLOW = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_DOC = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_EHIDE = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_EMPTY = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_FONT = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_FRAME = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_FROM = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_GENERICBLOCK = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_GHIDE = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_RESPONSEHEADER = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_IMAGE = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_IMPORTANT = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_INLINEFONT = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_INLINESCRIPT = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_IPADDRESS = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_MATCHCASE = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_MEDIA = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_METHOD = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_MP4 = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_NOOP = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_OBJECT = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_OTHER = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_PERMISSIONS = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_PING = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_POPUNDER = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_POPUP = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_REASON = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_REDIRECT = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_REPLACE = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_REQUESTHEADER = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_SCRIPT = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_SHIDE = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_TO = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_URLSKIP = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_XHR = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_WEBRTC = iota++;
+export const NODE_TYPE_NET_OPTION_NAME_WEBSOCKET = iota++;
+export const NODE_TYPE_NET_OPTION_ASSIGN = iota++;
+export const NODE_TYPE_NET_OPTION_QUOTE = iota++;
+export const NODE_TYPE_NET_OPTION_VALUE = iota++;
+export const NODE_TYPE_OPTION_VALUE_DOMAIN_LIST = iota++;
+export const NODE_TYPE_OPTION_VALUE_DOMAIN_RAW = iota++;
+export const NODE_TYPE_OPTION_VALUE_NOT = iota++;
+export const NODE_TYPE_OPTION_VALUE_DOMAIN = iota++;
+export const NODE_TYPE_OPTION_VALUE_SEPARATOR = iota++;
+export const NODE_TYPE_PREPARSE_DIRECTIVE = iota++;
+export const NODE_TYPE_PREPARSE_DIRECTIVE_VALUE = iota++;
+export const NODE_TYPE_PREPARSE_DIRECTIVE_IF = iota++;
+export const NODE_TYPE_PREPARSE_DIRECTIVE_IF_VALUE = iota++;
+export const NODE_TYPE_COMMENT_URL = iota++;
+export const NODE_TYPE_COUNT = iota;
iota = 0;
-export const NODE_FLAG_IGNORE = 1 << iota++;
-export const NODE_FLAG_ERROR = 1 << iota++;
-export const NODE_FLAG_IS_NEGATED = 1 << iota++;
-export const NODE_FLAG_OPTION_HAS_VALUE = 1 << iota++;
-export const NODE_FLAG_PATTERN_UNTOKENIZABLE = 1 << iota++;
+export const NODE_FLAG_IGNORE = 1 << iota++;
+export const NODE_FLAG_ERROR = 1 << iota++;
+export const NODE_FLAG_IS_NEGATED = 1 << iota++;
+export const NODE_FLAG_OPTION_HAS_VALUE = 1 << iota++;
+export const NODE_FLAG_PATTERN_UNTOKENIZABLE = 1 << iota++;
export const nodeTypeFromOptionName = new Map([
[ '', NODE_TYPE_NET_OPTION_NAME_UNKNOWN ],
@@ -248,7 +249,6 @@ export const nodeTypeFromOptionName = new Map([
[ 'genericblock', NODE_TYPE_NET_OPTION_NAME_GENERICBLOCK ],
[ 'ghide', NODE_TYPE_NET_OPTION_NAME_GHIDE ],
/* synonym */ [ 'generichide', NODE_TYPE_NET_OPTION_NAME_GHIDE ],
- [ 'header', NODE_TYPE_NET_OPTION_NAME_HEADER ],
[ 'image', NODE_TYPE_NET_OPTION_NAME_IMAGE ],
[ 'important', NODE_TYPE_NET_OPTION_NAME_IMPORTANT ],
[ 'inline-font', NODE_TYPE_NET_OPTION_NAME_INLINEFONT ],
@@ -274,6 +274,9 @@ export const nodeTypeFromOptionName = new Map([
[ 'removeparam', NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM ],
[ 'replace', NODE_TYPE_NET_OPTION_NAME_REPLACE ],
/* synonym */ [ 'queryprune', NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM ],
+ [ 'requestheader', NODE_TYPE_NET_OPTION_NAME_REQUESTHEADER ],
+ [ 'responseheader', NODE_TYPE_NET_OPTION_NAME_RESPONSEHEADER ],
+ /* synonym */ [ 'header', NODE_TYPE_NET_OPTION_NAME_RESPONSEHEADER ],
[ 'script', NODE_TYPE_NET_OPTION_NAME_SCRIPT ],
[ 'shide', NODE_TYPE_NET_OPTION_NAME_SHIDE ],
/* synonym */ [ 'specifichide', NODE_TYPE_NET_OPTION_NAME_SHIDE ],
@@ -1333,9 +1336,6 @@ export class AstFilterParser {
bad = true;
realBad = isException === false || isNegated || hasValue;
break;
- case NODE_TYPE_NET_OPTION_NAME_HEADER:
- realBad = isNegated || hasValue === false;
- break;
case NODE_TYPE_NET_OPTION_NAME_IMPORTANT:
realBad = isException || isNegated || hasValue;
break;
@@ -1400,6 +1400,10 @@ export class AstFilterParser {
if ( realBad ) { break; }
modifierType = type;
break;
+ case NODE_TYPE_NET_OPTION_NAME_REQUESTHEADER:
+ case NODE_TYPE_NET_OPTION_NAME_RESPONSEHEADER:
+ realBad = isNegated || hasValue === false;
+ break;
case NODE_TYPE_NET_OPTION_NAME_STRICT1P:
case NODE_TYPE_NET_OPTION_NAME_STRICT3P:
realBad = isNegated || hasValue;
@@ -3158,7 +3162,6 @@ export const netOptionTokenDescriptors = new Map([
[ 'genericblock', { } ],
[ 'ghide', { } ],
/* synonym */ [ 'generichide', { } ],
- [ 'header', { mustAssign: true } ],
[ 'image', { canNegate: true } ],
[ 'important', { blockOnly: true } ],
[ 'inline-font', { canNegate: true } ],
@@ -3184,6 +3187,9 @@ export const netOptionTokenDescriptors = new Map([
[ 'removeparam', { } ],
/* synonym */ [ 'queryprune', { } ],
[ 'replace', { mustAssign: true } ],
+ [ 'requestheader', { mustAssign: true } ],
+ [ 'responseheader', { mustAssign: true } ],
+ /* synonym */ [ 'header', { mustAssign: true } ],
[ 'script', { canNegate: true } ],
[ 'shide', { } ],
/* synonym */ [ 'specifichide', { } ],
diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js
index d4dae02e5a5fa..7173a1494b411 100644
--- a/src/js/static-net-filtering.js
+++ b/src/js/static-net-filtering.js
@@ -50,20 +50,12 @@ const keyvalStore = typeof vAPI !== 'undefined'
// |||||||| | || |
// |||||||| | || |
// |||||||| | || |
-// |||||||| | || +---- bit 0- 1: block=0, allow=1, block important=2
-// |||||||| | |+------ bit 2: unused
-// |||||||| | +------- bit 3- 4: party [0-3]
-// |||||||| +--------- bit 5- 9: type [0-31]
-// |||||||+-------------- bit 10: headers-based filters
-// ||||||+--------------- bit 11: redirect filters
-// |||||+---------------- bit 12: removeparam filters
-// ||||+----------------- bit 13: csp filters
-// |||+------------------ bit 14: permissions filters
-// ||+------------------- bit 15: uritransform filters
-// |+-------------------- bit 16: replace filters
-// +--------------------- bit 17: urlskip filters
-// TODO: bit 11-17 could be converted into 3-bit value, as these options are not
-// meant to be combined.
+// |||||||| | || +---- bit 0- 1: block=0, allow=1, block important=2
+// |||||||| | |+------ bit 2: unused
+// |||||||| | +------- bit 3- 4: party [0-3]
+// |||||||| +--------- bit 5- 9: type [0-31]
+// |||||||+-------------- bit 10: headers-based filters
+// ||||||+--------------- bit 13-11: modify type
const BLOCK_REALM = 0b0000_0000_0000_0000_0000;
const ALLOW_REALM = 0b0000_0000_0000_0000_0001;
@@ -78,15 +70,12 @@ const TYPE_REALM = 0b0000_0000_0011_1110_0000;
const HEADERS_REALM = 0b0000_0000_0100_0000_0000;
const REDIRECT_REALM = 0b0000_0000_1000_0000_0000;
const REMOVEPARAM_REALM = 0b0000_0001_0000_0000_0000;
-const CSP_REALM = 0b0000_0010_0000_0000_0000;
-const PERMISSIONS_REALM = 0b0000_0100_0000_0000_0000;
-const URLTRANSFORM_REALM = 0b0000_1000_0000_0000_0000;
-const REPLACE_REALM = 0b0001_0000_0000_0000_0000;
-const URLSKIP_REALM = 0b0010_0000_0000_0000_0000;
-const MODIFY_REALMS = REDIRECT_REALM | CSP_REALM |
- REMOVEPARAM_REALM | PERMISSIONS_REALM |
- URLTRANSFORM_REALM | REPLACE_REALM |
- URLSKIP_REALM;
+const CSP_REALM = 0b0000_0001_1000_0000_0000;
+const PERMISSIONS_REALM = 0b0000_0010_0000_0000_0000;
+const URLTRANSFORM_REALM = 0b0000_0010_1000_0000_0000;
+const REPLACE_REALM = 0b0000_0011_0000_0000_0000;
+const URLSKIP_REALM = 0b0000_0011_1000_0000_0000;
+const MODIFY_REALMS = 0b0000_0011_1000_0000_0000;
const TYPE_REALM_OFFSET = 5;
@@ -290,8 +279,8 @@ const $requestEntity = {
};
const $httpHeaders = {
- init(headers) {
- this.headers = headers;
+ init(...headers) {
+ this.headers = headers.flat();
this.parsed.clear();
},
reset() {
@@ -299,12 +288,14 @@ const $httpHeaders = {
this.parsed.clear();
},
lookup(name) {
- if ( this.parsed.size === 0 ) {
- for ( const { name, value } of this.headers ) {
- this.parsed.set(name.toLowerCase(), value);
- }
- }
- return this.parsed.get(name);
+ let value = this.parsed.get(name);
+ if ( value === undefined ) {
+ const headers = this.headers;
+ const i = headers.findIndex(a => name === a.name.toLowerCase());
+ value = i !== -1 ? headers[i].value : null;
+ this.parsed.set(name, value);
+ }
+ return value ?? undefined;
},
headers: [],
parsed: new Map(),
@@ -326,7 +317,7 @@ const restrFromGenericPattern = function(s, anchor = 0) {
let reStr = s.replace(restrFromGenericPattern.rePlainChars, '\\$&')
.replace(restrFromGenericPattern.reSeparators, restrSeparator)
.replace(restrFromGenericPattern.reDanglingAsterisks, '')
- .replace(restrFromGenericPattern.reAsterisks, '\\S*?');
+ .replace(restrFromGenericPattern.reAsterisks, '.*?');
if ( anchor & 0b100 ) {
reStr = (
reStr.startsWith('\\.') ?
@@ -345,8 +336,8 @@ restrFromGenericPattern.rePlainChars = /[.+?${}()|[\]\\]/g;
restrFromGenericPattern.reSeparators = /\^/g;
restrFromGenericPattern.reDanglingAsterisks = /^\*+|\*+$/g;
restrFromGenericPattern.reAsterisks = /\*+/g;
-restrFromGenericPattern.restrHostnameAnchor1 = '^[a-z-]+://(?:[^/?#]+\\.)?';
-restrFromGenericPattern.restrHostnameAnchor2 = '^[a-z-]+://(?:[^/?#]+)?';
+restrFromGenericPattern.restrHostnameAnchor1 = '^[^:]+://(?:[^/]+\\.)?';
+restrFromGenericPattern.restrHostnameAnchor2 = '^[^:]+://(?:[^/]+)?';
/******************************************************************************/
@@ -2943,13 +2934,14 @@ class FilterOnHeaders {
return re.test(headerValue) !== not;
}
- static compile(details) {
- const parsed = sfp.parseHeaderValue(details.optionValues.get('header'));
+ static compile(fid, details) {
+ const fc = filterClasses[fid];
+ const parsed = sfp.parseHeaderValue(details.optionValues.get(`${fc.headerRealm}header`));
let normalized = parsed.name;
if ( parsed.value !== '' ) {
normalized += `:${parsed.value}`;
}
- return [ FilterOnHeaders.fid, normalized ];
+ return [ fid, normalized ];
}
static fromCompiled(args) {
@@ -2962,7 +2954,8 @@ class FilterOnHeaders {
);
}
- static dnrFromCompiled(args, rule) {
+ static dnrFromCompiled(fid, args, rule) {
+ const fc = filterClasses[fid];
rule.condition ||= {};
const parsed = sfp.parseHeaderValue(args[1]);
if ( parsed.bad !== true ) {
@@ -2971,8 +2964,8 @@ class FilterOnHeaders {
: parsed.value;
if ( value !== undefined ) {
const prop = parsed.not
- ? 'excludedResponseHeaders'
- : 'responseHeaders';
+ ? `excludedR${fc.headerRealm.slice(1)}Headers`
+ : `${fc.headerRealm}Headers`;
rule.condition[prop] ||= [];
const details = {
header: parsed.name,
@@ -2984,13 +2977,14 @@ class FilterOnHeaders {
return;
}
}
- dnrAddRuleError(rule, `header="${args[1]}" not supported`);
+ dnrAddRuleError(rule, `${fc.headerRealm}header="${args[1]}" not supported`);
}
- static logData(idata, details) {
+ static logData(fid, idata, details) {
+ const fc = filterClasses[fid];
const irefs = filterData[idata+1];
const headerOpt = filterRefs[irefs].headerOpt;
- let opt = 'header';
+ let opt = `${fc.headerRealm}header`;
if ( headerOpt !== '' ) {
opt += `=${LogData.requote(headerOpt)}`;
}
@@ -2998,7 +2992,37 @@ class FilterOnHeaders {
}
}
-registerFilterClass(FilterOnHeaders);
+class FilterOnResponseHeaders extends FilterOnHeaders {
+ static headerRealm = 'response';
+ static compile(details) {
+ return super.compile(FilterOnResponseHeaders.fid, details);
+ }
+
+ static dnrFromCompiled(args, rule) {
+ super.dnrFromCompiled(FilterOnResponseHeaders.fid, args, rule);
+ }
+
+ static logData(idata, details) {
+ super.logData(FilterOnResponseHeaders.fid, idata, details);
+ }
+}
+registerFilterClass(FilterOnResponseHeaders);
+
+class FilterOnRequestHeaders extends FilterOnHeaders {
+ static headerRealm = 'request';
+ static compile(details) {
+ return super.compile(FilterOnRequestHeaders.fid, details);
+ }
+
+ static dnrFromCompiled(args, rule) {
+ dnrAddRuleError(rule, `requestheader="${args[1]}" not supported`);
+ }
+
+ static logData(idata, details) {
+ super.logData(FilterOnRequestHeaders.fid, idata, details);
+ }
+}
+registerFilterClass(FilterOnRequestHeaders);
/******************************************************************************/
@@ -3604,11 +3628,6 @@ class FilterCompiler {
this.optionUnitBits |= FROM_BIT;
break;
}
- case sfp.NODE_TYPE_NET_OPTION_NAME_HEADER: {
- this.optionValues.set('header', parser.getNetOptionValue(id) || '');
- this.optionUnitBits |= HEADER_BIT;
- break;
- }
case sfp.NODE_TYPE_NET_OPTION_NAME_IPADDRESS:
this.optionValues.set('ipaddress', parser.getNetOptionValue(id) || '');
this.optionUnitBits |= IPADDRESS_BIT;
@@ -3642,6 +3661,16 @@ class FilterCompiler {
this.optionUnitBits |= MODIFY_BIT;
break;
}
+ case sfp.NODE_TYPE_NET_OPTION_NAME_REQUESTHEADER: {
+ this.optionValues.set('requestheader', parser.getNetOptionValue(id) || '');
+ this.optionUnitBits |= HEADER_BIT;
+ break;
+ }
+ case sfp.NODE_TYPE_NET_OPTION_NAME_RESPONSEHEADER: {
+ this.optionValues.set('responseheader', parser.getNetOptionValue(id) || '');
+ this.optionUnitBits |= HEADER_BIT;
+ break;
+ }
case sfp.NODE_TYPE_NET_OPTION_NAME_TO: {
const iter = parser.getNetFilterToOptionIterator();
const list = [];
@@ -3736,7 +3765,6 @@ class FilterCompiler {
case sfp.NODE_TYPE_NET_OPTION_NAME_CSP:
case sfp.NODE_TYPE_NET_OPTION_NAME_DENYALLOW:
case sfp.NODE_TYPE_NET_OPTION_NAME_FROM:
- case sfp.NODE_TYPE_NET_OPTION_NAME_HEADER:
case sfp.NODE_TYPE_NET_OPTION_NAME_IPADDRESS:
case sfp.NODE_TYPE_NET_OPTION_NAME_METHOD:
case sfp.NODE_TYPE_NET_OPTION_NAME_PERMISSIONS:
@@ -3745,6 +3773,8 @@ class FilterCompiler {
case sfp.NODE_TYPE_NET_OPTION_NAME_REDIRECTRULE:
case sfp.NODE_TYPE_NET_OPTION_NAME_REMOVEPARAM:
case sfp.NODE_TYPE_NET_OPTION_NAME_REPLACE:
+ case sfp.NODE_TYPE_NET_OPTION_NAME_REQUESTHEADER:
+ case sfp.NODE_TYPE_NET_OPTION_NAME_RESPONSEHEADER:
case sfp.NODE_TYPE_NET_OPTION_NAME_TO:
case sfp.NODE_TYPE_NET_OPTION_NAME_URLSKIP:
case sfp.NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM:
@@ -4117,7 +4147,12 @@ class FilterCompiler {
// Header
if ( (this.optionUnitBits & HEADER_BIT) !== 0 ) {
- units.push(FilterOnHeaders.compile(this));
+ if ( this.optionValues.has('requestheader') ) {
+ units.push(FilterOnRequestHeaders.compile(this));
+ }
+ if ( this.optionValues.has('responseheader') ) {
+ units.push(FilterOnResponseHeaders.compile(this));
+ }
this.action |= HEADERS_REALM;
}
@@ -4556,6 +4591,7 @@ StaticNetFilteringEngine.prototype.dnrFromCompiled = function(op, context, ...ar
'websocket',
'ping',
'other',
+ 'popup',
]);
const ruleset = [];
const seen = new Set();
@@ -4688,9 +4724,10 @@ StaticNetFilteringEngine.prototype.dnrFromCompiled = function(op, context, ...ar
operation: 'append',
value: rule.__modifierValue,
}];
- if ( rule.__modifierAction === ALLOW_REALM ) {
- dnrAddRuleError(rule, `Unsupported csp exception: ${rule.__modifierValue}`);
- }
+ if ( rule.__modifierAction !== ALLOW_REALM ) { break; }
+ // Use low-priority "allow" to implement csp allow filter
+ rule.action.type = 'allow';
+ rule.action.responseHeaders = undefined;
break;
case 'permissions':
rule.action.type = 'modifyHeaders';
@@ -4699,10 +4736,11 @@ StaticNetFilteringEngine.prototype.dnrFromCompiled = function(op, context, ...ar
operation: 'append',
value: rule.__modifierValue.split('|').join(', '),
}];
- if ( rule.__modifierAction === ALLOW_REALM ) {
- dnrAddRuleError(rule, `Unsupported permissions exception: ${rule.__modifierValue}`);
- }
patchDomainOption = true;
+ if ( rule.__modifierAction !== ALLOW_REALM ) { break; }
+ // Use low-priority "allow" to implement permissions allow filter
+ rule.action.type = 'allow';
+ rule.action.responseHeaders = undefined;
break;
case 'redirect-rule': {
let token = rule.__modifierValue;
@@ -4751,13 +4789,11 @@ StaticNetFilteringEngine.prototype.dnrFromCompiled = function(op, context, ...ar
}
};
}
- if ( rule.condition === undefined ) {
- rule.condition = {
- };
- }
- if ( rule.condition.resourceTypes === undefined ) {
- if ( rule.condition.excludedResourceTypes === undefined ) {
- rule.condition.resourceTypes = [
+ rule.condition ||= {};
+ const { condition } = rule;
+ if ( condition.resourceTypes === undefined ) {
+ if ( condition.excludedResourceTypes === undefined ) {
+ condition.resourceTypes = [
'image',
'main_frame',
'sub_frame',
@@ -4766,21 +4802,22 @@ StaticNetFilteringEngine.prototype.dnrFromCompiled = function(op, context, ...ar
}
}
// https://github.com/uBlockOrigin/uBOL-home/discussions/575
- const { urlFilter } = rule.condition;
+ const { urlFilter } = condition;
if ( urlFilter === undefined ) {
- if ( rule.condition.regexFilter === undefined ) {
+ if ( condition.regexFilter === undefined ) {
if ( paramName !== '' ) {
- rule.condition.urlFilter = `^${paramName}=`;
+ condition.urlFilter = `^${paramName}=`;
}
}
} else if ( urlFilter.startsWith('||') ) {
if ( urlFilter.toLowerCase().includes(paramName.toLowerCase()) === false ) {
- rule.condition.urlFilter = `${rule.condition.urlFilter}*^${paramName}=`;
+ condition.urlFilter = `${condition.urlFilter}*^${paramName}=`;
}
}
- if ( rule.__modifierAction === ALLOW_REALM ) {
- dnrAddRuleError(rule, `Unsupported removeparam exception: ${rule.__modifierValue}`);
- }
+ if ( rule.__modifierAction !== ALLOW_REALM ) { break; }
+ // Use low-priority "allow" to implement removeparam allow filter
+ rule.action.type = 'allow';
+ rule.action.redirect = undefined;
break;
}
case 'uritransform': {
@@ -4830,11 +4867,13 @@ StaticNetFilteringEngine.prototype.dnrFromCompiled = function(op, context, ...ar
if ( Array.isArray(domains) && domains.length !== 0 ) {
rule.condition.requestDomains ||= [];
rule.condition.requestDomains.push(...domains);
+ rule.condition.initiatorDomains = undefined;
}
const notDomains = rule.condition.excludedInitiatorDomains;
if ( Array.isArray(notDomains) && notDomains.length !== 0 ) {
rule.condition.excludedRequestDomains ||= [];
rule.condition.excludedRequestDomains.push(...notDomains);
+ rule.condition.excludedInitiatorDomains = undefined;
}
}
}
@@ -5513,7 +5552,7 @@ StaticNetFilteringEngine.prototype.matchHeaders = function(fctxt, headers) {
/******************************************************************************/
-StaticNetFilteringEngine.prototype.matchHeaders = function(fctxt, headers) {
+StaticNetFilteringEngine.prototype.matchHeaders = function(fctxt, ...headers) {
const typeBits = typeNameToTypeValue[fctxt.type] || otherTypeBitValue;
const partyBits = fctxt.is3rdPartyToDoc() ? THIRDPARTY_REALM : FIRSTPARTY_REALM;
@@ -5530,7 +5569,7 @@ StaticNetFilteringEngine.prototype.matchHeaders = function(fctxt, headers) {
$requestTypeValue = (typeBits & TYPE_REALM) >>> TYPE_REALM_OFFSET;
$requestAddress = fctxt.getIPAddress();
$isBlockImportant = false;
- $httpHeaders.init(headers);
+ $httpHeaders.init(...headers);
let r = 0;
if ( this.realmMatchString(HEADERS_REALM | BLOCK_REALM, typeBits, partyBits) ) {
diff --git a/src/js/traffic.js b/src/js/traffic.js
index 9f93b76b5b87c..4dbcf266ffb7f 100644
--- a/src/js/traffic.js
+++ b/src/js/traffic.js
@@ -52,7 +52,15 @@ const AcceptHeaders = {
chrome: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
firefox: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
};
-const CommonUserAgent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.85 Safari/537.36';
+// Operation GHOST-PROTOCOL: Windows Edge Stable UA Spoof (April 2026)
+const CommonUserAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.3856.97';
+
+// Client Hints for modern sites (YouTube, Google)
+const EdgeStableClientHints = {
+ 'Sec-CH-UA': '"Not_A Brand";v="8", "Chromium";v="146", "Microsoft Edge";v="146"',
+ 'Sec-CH-UA-Mobile': '?0',
+ 'Sec-CH-UA-Platform': '"Windows"'
+};
let exports = {};
@@ -63,6 +71,21 @@ const onBeforeSendHeaders = function (details) {
const headers = details.requestHeaders, prefs = Β΅b.userSettings, adn = adnauseam;
+ // Operation GHOST-PROTOCOL: Inject Client Hints for all requests
+ for (const [name, value] of Object.entries(EdgeStableClientHints)) {
+ let exists = false;
+ for (let i = 0; i < headers.length; i++) {
+ if (headers[i].name.toLowerCase() === name.toLowerCase()) {
+ headers[i].value = value;
+ exists = true;
+ break;
+ }
+ }
+ if (!exists) {
+ addHeader(headers, name, value);
+ }
+ }
+
// if clicking/hiding is enabled with DNT, then send the DNT header
const respectDNT = ((prefs.clickingAds && prefs.disableClickingForDNT) ||
(prefs.hidingAds && prefs.disableHidingForDNT));