Compare commits

..

32 commits

Author SHA1 Message Date
Raymond Hill
3703f5efb8
New version for stable release 2026-03-10 20:42:16 -04:00
Raymond Hill
d92b6f315f
Update main.yml 2026-03-06 09:30:05 -05:00
Raymond Hill
8f544e53bc
New revision for dev build 2026-02-24 08:59:17 -05:00
Raymond Hill
b230effdbc
Update changelog 2026-02-24 08:55:51 -05:00
Raymond Hill
98d3e9500a
Improve json-edit-related scriptlets 2026-02-24 08:50:41 -05:00
Raymond Hill
f1689a9ab3
Make Firefox dev build auto-update 2026-02-16 10:24:19 -05:00
Raymond Hill
a85fc33ce8
Import translaetion work from https://crowdin.com/project/ublock 2026-02-11 18:05:25 -05:00
Raymond Hill
8d9832b4d6
[mv3] Remove "Steven Black’s Unified Hosts" list
Related feedback:
https://github.com/uBlockOrigin/uAssets/issues/17917#issuecomment-3876123107
2026-02-10 12:10:26 -05:00
Raymond Hill
2b63dcdcb6
[mv3] Force register content scripts when none are found
Possibly will fix cases of no content scripts found in Safari.

Related issue:
https://github.com/uBlockOrigin/uAssets/issues/30158#issuecomment-3869144473
2026-02-09 09:25:57 -05:00
Raymond Hill
aedaa97867
Import translation work from https://crowdin.com/project/ublock 2026-02-08 14:24:17 -05:00
Raymond Hill
93577ecca1
[mv3] Fix removeparam conversion
Related issue:
https://github.com/uBlockOrigin/uAssets/issues/30636
2026-02-08 09:56:25 -05:00
Raymond Hill
60a1b43d71
New revision for dev build 2026-02-07 11:50:41 -05:00
Raymond Hill
51c1b4a6ec
Update changelog 2026-02-07 11:50:20 -05:00
Raymond Hill
baffd32dab
Improve trusted-create-html scriptlet 2026-02-07 11:46:57 -05:00
Raymond Hill
2ce376cf1d
Improve prevent-fetch scriptlet
Related discussion:
https://github.com/uBlockOrigin/uAssets/issues/30731
2026-02-07 11:13:29 -05:00
Raymond Hill
efc59a2fc8
Revert "[mv3][safari] Bring back "AdGuard/uBO – URL Tracking Protection""
This reverts commit c7ff0f8c42.
2026-02-03 18:00:40 -05:00
Raymond Hill
c7ff0f8c42
[mv3][safari] Bring back "AdGuard/uBO – URL Tracking Protection"
Related issue:
https://github.com/uBlockOrigin/uBOL-home/issues/476
2026-02-03 17:38:15 -05:00
Raymond Hill
1ec05d4c0a
[mv3][safari] Remove image from removeparam
Revert https://github.com/gorhill/uBlock/commit/7aff7da77d for Safari.
2026-02-02 11:06:18 -05:00
Raymond Hill
916d2af99f
New revision for dev build 2026-02-01 10:57:37 -05:00
Raymond Hill
ac017e7664
Update changelog 2026-02-01 10:57:02 -05:00
Raymond Hill
a8ad95394d
Fix handling of extraMatch parameter in trusted-click-element scriptlet
Related feedback:
https://old.reddit.com/r/uBlockOrigin/comments/1qry49n/
2026-02-01 10:54:34 -05:00
Raymond Hill
719798329b
Fix misleading tooltip
Related issue:
https://github.com/uBlockOrigin/uBlock-issues/discussions/3934
2026-01-31 13:34:46 -05:00
Raymond Hill
d1877d448b
Import translation work from https://crowdin.com/project/ublock 2026-01-31 13:32:56 -05:00
Raymond Hill
0901e9b660
New revision for dev build 2026-01-30 11:16:07 -05:00
Raymond Hill
c00849befa
Update changelog 2026-01-30 11:15:49 -05:00
Raymond Hill
7d95c58408
Improve generateContentFn helper scriptlet
New directive: `join:[separator][sub-directives]`

[separator] is an author-defined two-character string to be used to split
the following sub-directives string. The sub-directives are fed back into
the helper scriptlet to generate sub-content, which will be joined into
a single string. Example:

...##+js(trusted-prevent-fetch, propstomatch, join:--length:10-20--[some literal content]--length:80-100-- push['ads'])
2026-01-30 11:05:57 -05:00
Raymond Hill
359cb070eb
[mv3] Fix minor regression in reported troubleshooting info 2026-01-29 08:38:53 -05:00
Raymond Hill
7aff7da77d
[mv3] Add image to generic removeparam filters
Related discussion:
https://github.com/uBlockOrigin/uBOL-home/issues/140#issuecomment-3817666188
2026-01-29 08:26:29 -05:00
Raymond Hill
9b4f41df94
[mv3] Improve conversion of removeparam filters to DNR rules
Related issue:
https://github.com/uBlockOrigin/uBOL-home/issues/140
2026-01-28 12:10:50 -05:00
Raymond Hill
2a0842f177
Update changelog 2026-01-26 10:13:38 -05:00
Raymond Hill
552761ec0c
New revision for dev build 2026-01-26 10:12:20 -05:00
Raymond Hill
168394440c
Improve prevent-xhr scriptlet 2026-01-26 10:11:05 -05:00
35 changed files with 430 additions and 357 deletions

View file

@ -12,7 +12,7 @@ jobs:
permissions:
contents: write # for creating release
name: Build packages
runs-on: ubuntu-latest
runs-on: ubuntu-slim
if: startsWith(github.ref, 'refs/tags/')
steps:
- name: Clone repository

View file

@ -1,3 +1,9 @@
- [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)
- [Fix handling of `extraMatch` parameter in `trusted-click-element` scriptlet](https://github.com/gorhill/uBlock/commit/a8ad95394d)
- [Improve `generateContentFn` helper scriptlet](https://github.com/gorhill/uBlock/commit/7d95c58408)
- [Improve `prevent-xhr` scriptlet](https://github.com/gorhill/uBlock/commit/168394440c)
- [Improve `proxyApplyFn` helper scriptlet](https://github.com/gorhill/uBlock/commit/18a8fc7675)
----------

View file

@ -3,13 +3,13 @@
"uBlock0@raymondhill.net": {
"updates": [
{
"version": "1.68.1.102",
"version": "1.69.1.4",
"browser_specific_settings": {
"gecko": {
"strict_min_version": "115.0"
}
},
"update_link": "https://github.com/gorhill/uBlock/releases/download/1.68.1rc2/uBlock0_1.68.1rc2.firefox.signed.xpi"
"update_link": "https://github.com/gorhill/uBlock/releases/download/1.69.1b4/uBlock0_1.69.1b4.firefox.signed.xpi"
}
]
}

2
dist/version vendored
View file

@ -1 +1 @@
1.69.1.0
1.70.0

View file

@ -384,11 +384,11 @@
"description": "Text for buttons used to restore content"
},
"resetToDefaultButton": {
"message": "Reset to default settings…",
"message": "إعادة تعيين إلى الإعدادات الافتراضية…",
"description": "Text for buttons used to reset configurations to default"
},
"resetToDefaultConfirm": {
"message": "All your custom settings will be removed. Do you really want to reset to default settings?",
"message": "سيتم حذف جميع إعداداتك المخصصة. هل تريد حقاً إعادة ضبط الإعدادات إلى الوضع الافتراضي؟",
"description": "Message asking user to confirm reset to default settings"
},
"dnrRulesWarning": {

View file

@ -136,7 +136,7 @@
"description": "Label of 'Troubleshooting information' section in 'Report a filter issue' page"
},
"supportS6P1S1": {
"message": "Um die Freiwilligen nicht mit doppelten Meldungen zu überlasten, vergewissern Sie sich bitte, dass das Problem noch nicht gemeldet wurde. <b>Hinweis:</b> Das Anklicken der Schaltfläche übermittelt den Ursprung der Seite an GitHub.",
"message": "Um die Freiwilligen nicht mit doppelten Meldungen zu überlasten, vergewissern Sie sich bitte, dass das Problem noch nicht gemeldet wurde. <b>Hinweis:</b> Das Anklicken der Schaltfläche übermittelt die Internetadresse an GitHub.",
"description": "A paragraph in the filter issue reporter section"
},
"supportFindSpecificButton": {
@ -156,11 +156,11 @@
"description": "An entry in the widget used to select the type of issue"
},
"supportS6Select1Option1": {
"message": "Zeigt Werbung oder Werbereste",
"message": "Zeigt Werbung oder deren Überreste",
"description": "An entry in the widget used to select the type of issue"
},
"supportS6Select1Option2": {
"message": "Hat Überdeckungen oder andere Belästigungen",
"message": "Enthält überdeckende oder belästigende Elemente",
"description": "An entry in the widget used to select the type of issue"
},
"supportS6Select1Option3": {
@ -168,7 +168,7 @@
"description": "An entry in the widget used to select the type of issue"
},
"supportS6Select1Option4": {
"message": "Hat Datenschutzprobleme",
"message": "Weist Datenschutzprobleme auf",
"description": "An entry in the widget used to select the type of issue"
},
"supportS6Select1Option5": {
@ -196,7 +196,7 @@
"description": "The header text for the default filtering mode section"
},
"defaultFilteringModeDescription": {
"message": "Der Standardfiltermodus wird durch individuelle Filtermodi für die jeweiligen Websites überschrieben. Sie können den Filtermodus auf jeder beliebigen Website entsprechend dem Modus anpassen, der auf dieser Website am besten funktioniert. Jeder Modus hat seine Vor- und Nachteile.",
"message": "Der Standardfiltermodus wird durch den eingestellten Filtermodus der jeweiligen Website überschrieben. Er kann auf jeder beliebigen Website individuell angepasst werden. Jeder Modus hat seine Vor- und Nachteile.",
"description": "This describes the default filtering mode setting"
},
"filteringMode0Name": {
@ -216,15 +216,15 @@
"description": "Name of blocking mode 3"
},
"basicFilteringModeDescription": {
"message": "Einfaches Netzwerkfiltern aus gewählten Filterlisten.\n\nErfordert keine Berechtigung zum Lesen und Ändern von Daten auf Websites.",
"message": "Einfaches Netzwerkfiltern durch ausgewählte Filterlisten.\n\nErfordert keine Berechtigung zum Lesen und Ändern von Daten auf Websites.",
"description": "This describes the 'basic' filtering mode"
},
"optimalFilteringModeDescription": {
"message": "Erweitertes Netzwerkfiltern plus spezifisches erweitertes Filtern aus ausgewählten Filterlisten.\n\nErfordert eine weitreichende Berechtigung zum Lesen und Ändern von Daten auf allen Websites.",
"message": "Erweitertes Netzwerkfiltern plus umfassendes Filtern durch ausgewählte Filterlisten.\n\nErfordert eine weitreichende Berechtigung zum Lesen und Ändern von Daten auf allen Websites.",
"description": "This describes the 'optimal' filtering mode"
},
"completeFilteringModeDescription": {
"message": "Erweitertes Netzwerkfiltern plus spezifisches und allgemeines erweitertes Filtern aus gewählten Filterlisten.\n\nErfordert eine weitreichende Berechtigung zum Lesen und Ändern von Daten auf allen Websites.\n\nDas allgemeine erweiterte Filtern kann zu einem höheren Ressourcenverbrauch der Webseite führen.",
"message": "Erweitertes Netzwerkfiltern plus umfassendes und allgemeines Filtern durch ausgewählte Filterlisten.\n\nErfordert eine weitreichende Berechtigung zum Lesen und Ändern von Daten auf allen Websites.\n\nDas allgemeine Filtern kann zu einem höheren Ressourcenverbrauch der Webseite führen.",
"description": "This describes the 'complete' filtering mode"
},
"noFilteringModeDescription": {
@ -264,7 +264,7 @@
"description": "Short description for a checkbox in the options page"
},
"settingsBackupRestoreLabel": {
"message": "Sicherung",
"message": "Backup",
"description": "The header text for the back up/restore section"
},
"settingsBackupRestoreSummary": {

View file

@ -384,11 +384,11 @@
"description": "Text for buttons used to restore content"
},
"resetToDefaultButton": {
"message": "Reset to default settings…",
"message": "ჩამოყრა ნაგულისხმევ პარამეტრებზე…",
"description": "Text for buttons used to reset configurations to default"
},
"resetToDefaultConfirm": {
"message": "All your custom settings will be removed. Do you really want to reset to default settings?",
"message": "ყველა მორგებული პარამეტრი მოცილდება. ნამდვილად გსურთ დაბრუნება ნაგულისხმევ პარამეტრებზე?",
"description": "Message asking user to confirm reset to default settings"
},
"dnrRulesWarning": {

View file

@ -52,7 +52,7 @@
"description": "English: Click to open the dashboard"
},
"popupMoreButton": {
"message": "자세히",
"message": "더 보기",
"description": "Label to be used to show popup panel sections"
},
"popupLessButton": {
@ -72,7 +72,7 @@
"description": "Header for a ruleset section in 'Filter lists pane'"
},
"3pGroupMalware": {
"message": "멀웨어 도메인",
"message": "멀웨어 및 보안",
"description": "Header for a ruleset section in 'Filter lists pane'"
},
"3pGroupAnnoyances": {
@ -168,7 +168,7 @@
"description": "An entry in the widget used to select the type of issue"
},
"supportS6Select1Option4": {
"message": "개인정보 보호 관련 이슈가 있습니다",
"message": "개인정보 보호 관련 문제가 있습니다",
"description": "An entry in the widget used to select the type of issue"
},
"supportS6Select1Option5": {
@ -184,7 +184,7 @@
"description": "An entry in the widget used to select the type of issue"
},
"supportS6Checkbox1": {
"message": "웹페이지를 \"NSFW\" (<a href=\"https://wikipedia.org/wiki/Not_safe_for_work\">“Not Safe For Work”</a>)로 분류",
"message": "웹페이지를 \"NSFW\" (<a href=\"https://ko.wikipedia.org/wiki/NSFW\">“Not Safe For Work”</a>)로 분류",
"description": "A checkbox to use for NSFW sites"
},
"supportReportSpecificButton": {
@ -252,7 +252,7 @@
"description": "Label for a checkbox in the options page"
},
"enableStrictBlockLegend": {
"message": "잠재적으로 좋지 않은 사이트로의 접속을 차단하고, 사용자가 진행할지 선택하도록 합니다.",
"message": "잠재적으로 유해할 수 있는 사이트로의 접속이 차단되며, 계속 진행할지 여부를 선택할 수 있는 옵션이 제공됩니다.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {

View file

@ -384,11 +384,11 @@
"description": "Text for buttons used to restore content"
},
"resetToDefaultButton": {
"message": "Reset to default settings…",
"message": "Đặt lại về cài đặt mặc định…",
"description": "Text for buttons used to reset configurations to default"
},
"resetToDefaultConfirm": {
"message": "All your custom settings will be removed. Do you really want to reset to default settings?",
"message": "Tất cả cài đặt tùy chỉnh của bạn sẽ bị xóa. Bạn có thực sự muốn đặt lại về cài đặt mặc định không?",
"description": "Message asking user to confirm reset to default settings"
},
"dnrRulesWarning": {

View file

@ -680,7 +680,7 @@ async function startSession() {
const shouldInject = isNewVersion || permissionsUpdated ||
isSideloaded && rulesetConfig.developerMode;
if ( shouldInject ) {
registerInjectables();
await registerInjectables();
}
// Cosmetic filtering-related content scripts cache fitlering data in
@ -730,6 +730,11 @@ async function start() {
scrmgr.onWakeupRun();
}
const scripts = await scrmgr.getRegisteredContentScripts();
if ( scripts.length === 0 ) {
registerInjectables();
}
toggleDeveloperMode(rulesetConfig.developerMode);
}

View file

@ -50,7 +50,7 @@ const reportedPage = (( ) => {
}
return {
hostname: parsedURL.hostname.replace(/^(m|mobile|www)\./, ''),
mode: url.searchParams.get('mode'),
siteMode: parseInt(url.searchParams.get('mode'), 10),
tabId: parseInt(url.searchParams.get('tabid'), 10) || 0,
};
} catch {

View file

@ -191,16 +191,6 @@
],
"homeURL": "https://github.com/uBlockOrigin/uAssets"
},
{
"id": "stevenblack-hosts",
"name": "Steven Blacks Unified Hosts (adware + malware)",
"enabled": false,
"excludedPlatforms": [ "safari" ],
"urls": [
"https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
],
"homeURL": "https://github.com/StevenBlack/hosts#readme"
},
{
"id": "ubol-tests",
"name": "uBO Lite Test Filters",

View file

@ -45,10 +45,11 @@ function patchRule(rule, out) {
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');
condition.resourceTypes = resourceTypes.filter(a => a !== 'main_frame' && a !== 'image');
if ( condition.resourceTypes.length === 0 ) { return; }
}
}

View file

@ -220,7 +220,7 @@
"description": "Tooltip when hovering the top-most cell of the global-rules column."
},
"popupTipLocalRules": {
"message": "القواعد الموضعية: هذا العمود يتعلق بالقواعد التي تنطبق فقط على الموقع المزار حاليا.\nالقواعد الموضعية تحل محل القواعد العامة.",
"message": "الشروط المؤقتة: هذا العمود يتعلق بالقواعد التي تنطبق فقط على الموقع المزار حاليا.\nالشروط المؤقتة تحل محل الشروط العامة.",
"description": "Tooltip when hovering the top-most cell of the local-rules column."
},
"popupTipSaveRules": {

View file

@ -220,7 +220,7 @@
"description": "Tooltip when hovering the top-most cell of the global-rules column."
},
"popupTipLocalRules": {
"message": "Local rules: this column is for rules which apply to the current site only.\nLocal rules override global rules.",
"message": "Local rules: this column is for rules which apply to the current site only.",
"description": "Tooltip when hovering the top-most cell of the local-rules column."
},
"popupTipSaveRules": {

View file

@ -220,7 +220,7 @@
"description": "Tooltip when hovering the top-most cell of the global-rules column."
},
"popupTipLocalRules": {
"message": "Local rules: this column is for rules which apply to the current site only.\nLocal rules override global rules.",
"message": "Local rules: this column is for rules which apply to the current site only.",
"description": "Tooltip when hovering the top-most cell of the local-rules column."
},
"popupTipSaveRules": {

View file

@ -220,7 +220,7 @@
"description": "Tooltip when hovering the top-most cell of the global-rules column."
},
"popupTipLocalRules": {
"message": "Lokale regler: Denne kolonne er til regler kun gældene dette websted.\nLokale regler tilsidesætter globale regler.",
"message": "Lokale regler: denne søjle viser de regler der gælder kun på denne side.\nLokale regler overskriver globale regler.",
"description": "Tooltip when hovering the top-most cell of the local-rules column."
},
"popupTipSaveRules": {

View file

@ -216,11 +216,11 @@
"description": "Label to be used to hide popup panel sections"
},
"popupTipGlobalRules": {
"message": "Globale Regeln: Diese Spalte ist für Regeln, die für alle Websites gelten.",
"message": "Globale Regeln: Die Regeln in dieser Spalte gelten für alle Websites.",
"description": "Tooltip when hovering the top-most cell of the global-rules column."
},
"popupTipLocalRules": {
"message": "Lokale Regeln: Diese Spalte ist für Regeln, die nur für die aktuelle Website gelten.\nLokale Regeln überschreiben globale Regeln.",
"message": "Lokale Regeln: Die Regeln in dieser Spalte gelten nur für diese Website.",
"description": "Tooltip when hovering the top-most cell of the local-rules column."
},
"popupTipSaveRules": {
@ -416,7 +416,7 @@
"description": "English: Last restore:"
},
"settingsLastBackupPrompt": {
"message": "Letzte Sicherung:",
"message": "Letztes Backup:",
"description": "English: Last backup:"
},
"3pListsOfBlockedHostsPrompt": {
@ -960,7 +960,7 @@
"description": "Header of 'Report a filter issue' section in Support pane"
},
"supportS6P1S1": {
"message": "Um die Freiwilligen nicht mit doppelten Meldungen zu überlasten, vergewissern Sie sich bitte, dass das Problem noch nicht gemeldet wurde. <b>Hinweis:</b> Das Anklicken der Schaltfläche übermittelt den Ursprung der Seite an GitHub.",
"message": "Um die Freiwilligen nicht mit doppelten Meldungen zu überlasten, vergewissern Sie sich bitte, dass das Problem noch nicht gemeldet wurde. <b>Hinweis:</b> Das Anklicken der Schaltfläche übermittelt die Internetadresse an GitHub.",
"description": "A paragraph in the filter issue reporter section"
},
"supportS6P2S1": {
@ -984,11 +984,11 @@
"description": "An entry in the widget used to select the type of issue"
},
"supportS6Select1Option1": {
"message": "Zeigt Werbung oder Werbereste",
"message": "Zeigt Werbung oder deren Überreste",
"description": "An entry in the widget used to select the type of issue"
},
"supportS6Select1Option2": {
"message": "Hat Überdeckungen oder andere Belästigungen",
"message": "Enthält überdeckende oder belästigende Elemente",
"description": "An entry in the widget used to select the type of issue"
},
"supportS6Select1Option3": {
@ -996,7 +996,7 @@
"description": "An entry in the widget used to select the type of issue"
},
"supportS6Select1Option4": {
"message": "Hat Datenschutzprobleme",
"message": "Weist Datenschutzprobleme auf",
"description": "An entry in the widget used to select the type of issue"
},
"supportS6Select1Option5": {
@ -1060,7 +1060,7 @@
"description": "Text for button to create a backup of all settings"
},
"aboutBackupFilename": {
"message": "ublock-sicherung_{{datetime}}.txt",
"message": "ublock-backup_{{datetime}}.txt",
"description": "English: my-ublock-backup_{{datetime}}.txt"
},
"aboutRestoreDataButton": {
@ -1072,7 +1072,7 @@
"description": "English: Reset to default settings..."
},
"aboutRestoreDataConfirm": {
"message": "Alle Einstellungen werden überschrieben und auf den Stand vom {{time}} gebracht. Anschließend wird uBlock₀ neu gestartet.\n\nSollen die aktuellen Einstellungen durch die Sicherung ersetzt werden?",
"message": "Alle Einstellungen werden überschrieben und auf den Stand vom {{time}} gebracht. Anschließend wird uBlock₀ neu gestartet.\n\nSollen die aktuellen Einstellungen durch das Backup ersetzt werden?",
"description": "Message asking user to confirm restore"
},
"aboutRestoreDataError": {

View file

@ -220,7 +220,7 @@
"description": "Tooltip when hovering the top-most cell of the global-rules column."
},
"popupTipLocalRules": {
"message": "Local rules: this column is for rules which apply to the current site only.\nLocal rules override global rules.",
"message": "Local rules: this column is for rules which apply to the current site only.",
"description": "Tooltip when hovering the top-most cell of the local-rules column."
},
"popupTipSaveRules": {

View file

@ -220,7 +220,7 @@
"description": "Tooltip when hovering the top-most cell of the global-rules column."
},
"popupTipLocalRules": {
"message": "Local rules: this column is for rules which apply to the current site only.\nLocal rules override global rules.",
"message": "Local rules: this column is for rules which apply to the current site only.",
"description": "Tooltip when hovering the top-most cell of the local-rules column."
},
"popupTipSaveRules": {

View file

@ -220,7 +220,7 @@
"description": "Tooltip when hovering the top-most cell of the global-rules column."
},
"popupTipLocalRules": {
"message": "Local rules: this column is for rules which apply to the current site only.\nLocal rules override global rules.",
"message": "Local rules: this column is for rules which apply to the current site only.",
"description": "Tooltip when hovering the top-most cell of the local-rules column."
},
"popupTipSaveRules": {

View file

@ -220,7 +220,7 @@
"description": "Tooltip when hovering the top-most cell of the global-rules column."
},
"popupTipLocalRules": {
"message": "Local rules: this column is for rules which apply to the current site only.\nLocal rules override global rules.",
"message": "Local rules: this column is for rules which apply to the current site only.",
"description": "Tooltip when hovering the top-most cell of the local-rules column."
},
"popupTipSaveRules": {

View file

@ -12,7 +12,7 @@
"description": "English: uBlock₀ — Dashboard"
},
"dashboardUnsavedWarning": {
"message": "경고! 저장되지 않은 변경 사항이 있습니다",
"message": "경고: 저장되지 않은 변경 사항이 있습니다!",
"description": "A warning in the dashboard when navigating away from unsaved changes"
},
"dashboardUnsavedWarningStay": {
@ -72,7 +72,7 @@
"description": "English: Click: disable/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page."
},
"popupPowerSwitchInfo1": {
"message": "클릭: 이 사이트에서 uBlock₀을 끕니다.\n\nCtrl+클릭: 이 페이지에서만 uBlock₀을 끕니다.",
"message": "클릭: 이 사이트에서 uBlock₀을 비활성화합니다.\nCtrl+클릭: 이 페이지에서만 uBlock₀을 비활성화합니다.",
"description": "Message to be read by screen readers"
},
"popupPowerSwitchInfo2": {
@ -96,7 +96,7 @@
"description": "English: since install"
},
"popupOr": {
"message": "/",
"message": "또는",
"description": "English: or"
},
"popupBlockedOnThisPage_v2": {
@ -128,7 +128,7 @@
"description": "Tooltip used for the logger icon in the panel"
},
"popupTipReport": {
"message": "이 사이트의 이슈 신고",
"message": "이 사이트의 문제 신고",
"description": "Tooltip used for the 'chat' icon in the panel"
},
"popupTipNoPopups": {
@ -136,27 +136,27 @@
"description": "Tooltip for the no-popups per-site switch"
},
"popupTipNoPopups1": {
"message": "클릭하여 이 사이트에서 모든 팝업을 차단합니다.",
"message": "클릭하여 이 사이트에서 모든 팝업을 차단합니다",
"description": "Tooltip for the no-popups per-site switch"
},
"popupTipNoPopups2": {
"message": "클릭하여 이 사이트에서 모든 팝업 차단을 해제합니다.",
"message": "클릭하여 이 사이트에서 팝업 차단을 해제합니다",
"description": "Tooltip for the no-popups per-site switch"
},
"popupTipNoLargeMedia": {
"message": "이 사이트에서만 적용되는 대형 미디어 구성요소 차단 기능을 켜고 끕니다.",
"message": "이 사이트에서만 적용되는 대용량 미디어 요소 차단 기능을 켜고 끕니다",
"description": "Tooltip for the no-large-media per-site switch"
},
"popupTipNoLargeMedia1": {
"message": "클릭하여 이 사이트에서 대용량 미디어를 차단합니다.",
"message": "클릭하여 이 사이트에서 대용량 미디어를 차단합니다",
"description": "Tooltip for the no-large-media per-site switch"
},
"popupTipNoLargeMedia2": {
"message": "클릭하여 이 사이트에서 대용량 미디어 차단을 해제합니다.",
"message": "클릭하여 이 사이트에서 대용량 미디어 차단을 해제합니다",
"description": "Tooltip for the no-large-media per-site switch"
},
"popupTipNoCosmeticFiltering": {
"message": "이 사이트에서 시각적 필터링 토글",
"message": "이 사이트에서 요소 숨김 필터링 토글",
"description": "Tooltip for the no-cosmetic-filtering per-site switch"
},
"popupTipNoCosmeticFiltering1": {
@ -168,23 +168,23 @@
"description": "Tooltip for the no-cosmetic-filtering per-site switch"
},
"popupTipNoRemoteFonts": {
"message": "이 사이트에서 글꼴조정 차단 토글",
"message": "이 사이트에서 외부 폰트 차단 토글",
"description": "Tooltip for the no-remote-fonts per-site switch"
},
"popupTipNoRemoteFonts1": {
"message": "클릭하여 이 사이트에서 외부 폰트를 차단합니다.",
"message": "클릭하여 이 사이트에서 외부 폰트를 차단합니다",
"description": "Tooltip for the no-remote-fonts per-site switch"
},
"popupTipNoRemoteFonts2": {
"message": "클릭하여 이 사이트에서 더 이상 외부 폰트를 차단하지 않습니다.",
"message": "클릭하여 이 사이트에서 외부 폰트 차단을 해제합니다",
"description": "Tooltip for the no-remote-fonts per-site switch"
},
"popupTipNoScripting1": {
"message": "클릭하여 이 사이트에서 JavaScript 비활성화",
"message": "클릭하여 이 사이트에서 JavaScript 비활성화합니다",
"description": "Tooltip for the no-scripting per-site switch"
},
"popupTipNoScripting2": {
"message": "클릭하여 이 사이트에서 JavaScript 활성화",
"message": "클릭하여 이 사이트에서 JavaScript 활성화합니다",
"description": "Tooltip for the no-scripting per-site switch"
},
"popupNoPopups_v2": {
@ -192,7 +192,7 @@
"description": "Caption for the no-popups per-site switch"
},
"popupNoLargeMedia_v2": {
"message": " 미디어 요소",
"message": "대용량 미디어 요소",
"description": "Caption for the no-large-media per-site switch"
},
"popupNoCosmeticFiltering_v2": {
@ -204,11 +204,11 @@
"description": "Caption for the no-remote-fonts per-site switch"
},
"popupNoScripting_v2": {
"message": "자바스크립트",
"message": "JavaScript",
"description": "Caption for the no-scripting per-site switch"
},
"popupMoreButton_v2": {
"message": "더보기",
"message": "더 보기",
"description": "Label to be used to show popup panel sections"
},
"popupLessButton_v2": {
@ -240,11 +240,11 @@
"description": ""
},
"popup3pAnyRulePrompt": {
"message": "보조 필터",
"message": "서드 파티 필터",
"description": ""
},
"popup3pPassiveRulePrompt": {
"message": "보조 CSS/이미지",
"message": "서드 파티 CSS/이미지",
"description": ""
},
"popupInlineScriptRulePrompt": {
@ -252,15 +252,15 @@
"description": ""
},
"popup1pScriptRulePrompt": {
"message": "스크립트",
"message": "스크립트",
"description": ""
},
"popup3pScriptRulePrompt": {
"message": "보조 스크립트",
"message": "서드 파티 스크립트",
"description": ""
},
"popup3pFrameRulePrompt": {
"message": "보조 프레임",
"message": "서드 파티 프레임",
"description": ""
},
"popupHitDomainCountPrompt": {
@ -300,11 +300,11 @@
"description": "Element picker preview mode: will cause the elements matching the current filter to be removed from the page"
},
"pickerNetFilters": {
"message": " 필터",
"message": "네트워크 필터",
"description": "English: header for a type of filter in the element picker dialog"
},
"pickerCosmeticFilters": {
"message": "표면 필터",
"message": "요소 숨김 필터",
"description": "English: Cosmetic filters"
},
"pickerCosmeticFiltersHint": {
@ -316,7 +316,7 @@
"description": "An entry in the browser's contextual menu"
},
"settingsCollapseBlockedPrompt": {
"message": "차단된 구성 요소의 자리 감추기",
"message": "차단된 요소의 자리 감추기",
"description": "English: Hide placeholders of blocked elements"
},
"settingsIconBadgePrompt": {
@ -332,7 +332,7 @@
"description": "English: Make use of context menu where appropriate"
},
"settingsColorBlindPrompt": {
"message": "색맹 최적화",
"message": "색맹 접근성 향상",
"description": "English: Color-blind friendly"
},
"settingsAppearance": {
@ -376,7 +376,7 @@
"description": ""
},
"settingsNoCosmeticFilteringPrompt": {
"message": "표면 필터 비활성화",
"message": "요소 숨김 필터 비활성화",
"description": ""
},
"settingsNoLargeMediaPrompt": {
@ -384,7 +384,7 @@
"description": ""
},
"settingsNoRemoteFontsPrompt": {
"message": "글꼴 조종 차단",
"message": "외부 폰트 차단",
"description": ""
},
"settingsNoScriptingPrompt": {
@ -420,7 +420,7 @@
"description": "English: Last backup:"
},
"3pListsOfBlockedHostsPrompt": {
"message": "{{netFilterCount}} 네트워크 필터 {{cosmeticFilterCount}} 표면 필터:",
"message": "{{netFilterCount}} 네트워크 필터 {{cosmeticFilterCount}} 요소 숨김 필터:",
"description": "Appears at the top of the _3rd-party filters_ pane"
},
"3pListsOfBlockedHostsPerListStats": {
@ -440,7 +440,7 @@
"description": "A button in the in the _3rd-party filters_ pane"
},
"3pParseAllABPHideFiltersPrompt1": {
"message": "표면 필터 분석 및 적용",
"message": "요소 숨김 필터 분석 및 적용",
"description": "English: Parse and enforce Adblock+ element hiding filters."
},
"3pParseAllABPHideFiltersInfo": {
@ -448,7 +448,7 @@
"description": "Describes the purpose of the 'Parse and enforce cosmetic filters' feature."
},
"3pIgnoreGenericCosmeticFilters": {
"message": "전체적 표면 필터 무시",
"message": "범용 요소 숨김 필터 무시",
"description": "This will cause uBO to ignore all generic cosmetic filters."
},
"3pIgnoreGenericCosmeticFiltersInfo": {
@ -468,7 +468,7 @@
"description": "English: Apply changes"
},
"3pGroupDefault": {
"message": "Ublock₀ 제공",
"message": "기본 내장",
"description": "Filter lists section name"
},
"3pGroupAds": {
@ -476,11 +476,11 @@
"description": "Filter lists section name"
},
"3pGroupPrivacy": {
"message": "개인정보",
"message": "개인정보 보호",
"description": "Filter lists section name"
},
"3pGroupMalware": {
"message": "멀웨어 도메인",
"message": "멀웨어 및 보안",
"description": "Filter lists section name"
},
"3pGroupSocial": {
@ -488,11 +488,11 @@
"description": "Filter lists section name"
},
"3pGroupCookies": {
"message": "쿠키 알림",
"message": "쿠키 공지",
"description": "Filter lists section name"
},
"3pGroupAnnoyances": {
"message": "골칫거리",
"message": "방해 요소",
"description": "Filter lists section name"
},
"3pGroupMultipurpose": {
@ -500,7 +500,7 @@
"description": "Filter lists section name"
},
"3pGroupRegions": {
"message": "지역, 언어",
"message": "지역 언어",
"description": "Filter lists section name"
},
"3pGroupCustom": {
@ -740,7 +740,7 @@
"description": "A keyword in the built-in row filtering expression"
},
"loggerRowFiltererBuiltin3p": {
"message": "보조",
"message": "서드 파티",
"description": "A keyword in the built-in row filtering expression"
},
"loggerEntryDetailsHeader": {
@ -796,7 +796,7 @@
"description": "Small header to identify the static filtering section"
},
"loggerStaticFilteringSentence": {
"message": "네트워크 요청이 {{type}} 일 경우,{{br}}URL 주소가 {{url}} 와 일치하고{{br}}{{origin}} 에서 비롯 되었을 경우 {{action}} 한다.{{br}}그리고 다음 예외 필터와 일치할 경우 {{importance}} 한다.",
"message": "네트워크 요청이 {{type}} 일 경우,{{br}}URL 주소가 {{url}} 와 일치하고{{br}}{{origin}} 에서 비롯되었을 경우 {{action}} 한다.{{br}}그리고 다음 예외 필터와 일치할 경우 {{importance}} 한다.",
"description": "Used in the static filtering wizard"
},
"loggerStaticFilteringSentencePartBlock": {
@ -836,7 +836,7 @@
"description": "Below this sentence, the filter list(s) in which the filter was found"
},
"loggerStaticFilteringFinderSentence2": {
"message": "활성화된 정적 필터 목록에서 <code>{{filter}}</code>를 찾지 못했습니다",
"message": "현재 활성화된 필터 목록에서 정적 필터를 찾을 수 없습니다",
"description": "Message to show when a filter cannot be found in any filter lists"
},
"loggerSettingDiscardPrompt": {
@ -928,11 +928,11 @@
"description": "Header of 'Filter issues' section in Support pane"
},
"supportS3P1": {
"message": "특정 웹사이트에서 발생하는 필터 이슈는 <span data-url=\"https://github.com/uBlockOrigin/uAssets/issues?q=is%3Aissue\"><code>uBlockOrigin/uAssets</code> 이슈 트래커</span>에 보고해주세요. GitHub 계정이 필요합니다.",
"message": "특정 웹사이트에서 발생하는 필터 문제는 <span data-url=\"https://github.com/uBlockOrigin/uAssets/issues?q=is%3Aissue\"><code>uBlockOrigin/uAssets</code> 이슈 트래커</span>에 보고해주세요. GitHub 계정이 필요합니다",
"description": "First paragraph of 'Filter issues' section in Support pane"
},
"supportS3P2": {
"message": "<b>중요:</b> uBlock Origin과 유사한 목적의 다른 차단기를 함께 사용하지 마세요. 특정 웹사이트에서 필터 문제가 발생할 수 있습니다.",
"message": "<b>중요:</b> uBlock Origin과 유사한 목적의 다른 차단기를 함께 사용하지 마세요. 일부 웹사이트에서 필터 문제가 발생할 수 있습니다.",
"description": "Second paragraph of 'Filter issues' section in Support pane"
},
"supportS3P3": {
@ -952,23 +952,23 @@
"description": "Header of 'Troubleshooting Information' section in Support pane"
},
"supportS5P1": {
"message": "다음은 자원 봉사자들이 문제를 해결하는 데에 도움을 줄 때 유용할 수 있는 기술적인 정보입니다.",
"message": "다음은 자원봉사자들이 문제를 해결을 돕는 데 유용할 수 있는 기술적인 정보입니다.",
"description": "First paragraph of 'Troubleshooting Information' section in Support pane"
},
"supportS6H": {
"message": "필터 이슈 신고",
"message": "필터 문제 신고",
"description": "Header of 'Report a filter issue' section in Support pane"
},
"supportS6P1S1": {
"message": "봉사자들이 중복 신고로 인해 부담을 겪지 않도록, 해당 이슈가 이미 신고되지는 않았는지 확인해주시기 바랍니다.",
"message": "자원봉사자들이 중복 신고로 인해 부담을 겪지 않도록, 해당 문제가 이미 신고되지는 않았는지 확인해주시기 바랍니다. <b>중요:</b> 버튼을 클릭하면 페이지의 출처가 GitHub로 전송됩니다.",
"description": "A paragraph in the filter issue reporter section"
},
"supportS6P2S1": {
"message": "필터 목록은 매일 갱신됩니다. 최신 필터 목록에서 문제가 이미 해결되진 않았는지 확인하세요.",
"message": "필터 목록은 매일 업데이트됩니다. 최신 필터 목록에서 문제가 이미 해결되었는지 확인해 주세요.",
"description": "A paragraph in the filter issue reporter section"
},
"supportS6P2S2": {
"message": "문제가 생긴 웹페이지를 새로고침해도 문제가 여전히 남아 있는지 확인하세요.",
"message": "문제가 있는 웹페이지를 새로고침한 후에도 문제가 여전히 발생하는지 확인해 주세요.",
"description": "A paragraph in the filter issue reporter section"
},
"supportS6URL": {
@ -1012,11 +1012,11 @@
"description": "An entry in the widget used to select the type of issue"
},
"supportS6Checkbox1": {
"message": "웹페이지를 \"NSFW\" (<a href=\"https://wikipedia.org/wiki/Not_safe_for_work\">“Not Safe For Work”</a>)로 분류",
"message": "웹 페이지를 “NSFW” (<a href=\"https://wikipedia.org/wiki/Not_safe_for_work\">“Not Safe For Work”</a>)로 분류",
"description": "A checkbox to use for NSFW sites"
},
"aboutPrivacyPolicy": {
"message": "개인정보취급방침",
"message": "개인정보 처리방침",
"description": "Link to privacy policy on GitHub (English)"
},
"aboutChangelog": {
@ -1092,31 +1092,31 @@
"description": "For the button used to subscribe to a filter list"
},
"elapsedOneMinuteAgo": {
"message": "1 분 전",
"message": "1분 전",
"description": "English: a minute ago"
},
"elapsedManyMinutesAgo": {
"message": "{{value}} 분 전",
"message": "{{value}}분 전",
"description": "English: {{value}} minutes ago"
},
"elapsedOneHourAgo": {
"message": "1 시간 전",
"message": "1시간 전",
"description": "English: an hour ago"
},
"elapsedManyHoursAgo": {
"message": "{{value}} 시간 전",
"message": "{{value}}시간 전",
"description": "English: {{value}} hours ago"
},
"elapsedOneDayAgo": {
"message": "1 일 전",
"message": "1일 전",
"description": "English: a day ago"
},
"elapsedManyDaysAgo": {
"message": "{{value}} 일 전",
"message": "{{value}}일 전",
"description": "English: {{value}} days ago"
},
"showDashboardButton": {
"message": "대보드 보기",
"message": "대보드 보기",
"description": "Firefox/Fennec-specific: Show Dashboard"
},
"showNetworkLogButton": {
@ -1164,7 +1164,7 @@
"description": "English: Disable strict blocking for {{hostname}} ..."
},
"docblockedDisableTemporary": {
"message": "이번만",
"message": "일시적으로",
"description": "English: Temporarily"
},
"docblockedDisablePermanent": {
@ -1244,7 +1244,7 @@
"description": "An entry in the browser's contextual menu"
},
"contextMenuTemporarilyAllowLargeMediaElements": {
"message": "이번만 대형 미디어 구성요소 허용",
"message": "일시적으로 대용량 미디어 요소 허용",
"description": "A context menu entry, present when large media elements have been blocked on the current site"
},
"contextMenuViewSource": {

View file

@ -220,7 +220,7 @@
"description": "Tooltip when hovering the top-most cell of the global-rules column."
},
"popupTipLocalRules": {
"message": "Local rules: this column is for rules which apply to the current site only.\nLocal rules override global rules.",
"message": "Local rules: this column is for rules which apply to the current site only.",
"description": "Tooltip when hovering the top-most cell of the local-rules column."
},
"popupTipSaveRules": {

View file

@ -220,7 +220,7 @@
"description": "Tooltip when hovering the top-most cell of the global-rules column."
},
"popupTipLocalRules": {
"message": "Lokale regels: deze kolom is voor regels die alleen op de huidige website van toepassing zijn.\nLokale regels hebben voorrang op algemene regels.",
"message": "Lokale regels: deze kolom is voor regels die alleen op de huidige website van toepassing zijn.",
"description": "Tooltip when hovering the top-most cell of the local-rules column."
},
"popupTipSaveRules": {

View file

@ -220,7 +220,7 @@
"description": "Tooltip when hovering the top-most cell of the global-rules column."
},
"popupTipLocalRules": {
"message": "Local rules: this column is for rules which apply to the current site only.\nLocal rules override global rules.",
"message": "Local rules: this column is for rules which apply to the current site only.",
"description": "Tooltip when hovering the top-most cell of the local-rules column."
},
"popupTipSaveRules": {

View file

@ -220,7 +220,7 @@
"description": "Tooltip when hovering the top-most cell of the global-rules column."
},
"popupTipLocalRules": {
"message": "Lokala regler: den här kolumnen avser endast regler som gäller för den aktuella webbplatsen.\nLokala regler åsidosätter globala regler.",
"message": "Lokala regler: denna kolumn avser endast regler som gäller för den aktuella webbplatsen.\nLokala regler åsidosätter globala regler.",
"description": "Tooltip when hovering the top-most cell of the local-rules column."
},
"popupTipSaveRules": {

View file

@ -220,7 +220,7 @@
"description": "Tooltip when hovering the top-most cell of the global-rules column."
},
"popupTipLocalRules": {
"message": "Локальні правила: цей стовпчик для правил, що застосовуються лише для цього сайту.\nЛокальні правила замінюють глобальні.",
"message": "Локальні правила: цей стовпчик для правил, що застосовуються лише цього сайту.\nЛокальні правила замінюють глобальні.",
"description": "Tooltip when hovering the top-most cell of the local-rules column."
},
"popupTipSaveRules": {

View file

@ -486,7 +486,10 @@ export class JSONPath {
if ( outcome ) { return k; }
}
#modifyVal(obj, key) {
const { modify, rval } = this.#compiled;
let { modify, rval } = this.#compiled;
if ( typeof rval === 'string' ) {
rval = rval.replace('${now}', `${Date.now()}`);
}
switch ( modify ) {
case undefined:
obj[key] = rval;

View file

@ -71,15 +71,12 @@ function trustedCreateHTML(
const duration = parseInt(durationStr, 10);
const domParser = new DOMParser();
const externalDoc = domParser.parseFromString(htmlStr, 'text/html');
const docFragment = new DocumentFragment();
const toRemove = [];
const toAppend = [];
while ( externalDoc.body.firstChild !== null ) {
const imported = document.adoptNode(externalDoc.body.firstChild);
docFragment.appendChild(imported);
if ( isNaN(duration) ) { continue; }
toRemove.push(imported);
toAppend.push(document.adoptNode(externalDoc.body.firstChild));
}
if ( docFragment.firstChild === null ) { return; }
if ( toAppend.length === 0 ) { return; }
const toRemove = [];
const remove = ( ) => {
for ( const node of toRemove ) {
if ( node.parentNode === null ) { continue; }
@ -87,10 +84,21 @@ function trustedCreateHTML(
}
safe.uboLog(logPrefix, 'Node(s) removed');
};
const appendOne = (target, nodes) => {
for ( const node of nodes ) {
target.append(node);
if ( isNaN(duration) ) { continue; }
toRemove.push(node);
}
};
const append = ( ) => {
const parent = document.querySelector(parentSelector);
if ( parent === null ) { return false; }
parent.append(docFragment);
const targets = document.querySelectorAll(parentSelector);
if ( targets.length === 0 ) { return false; }
const limit = Math.min(targets.length, extraArgs.limit || 1) - 1;
for ( let i = 0; i < limit; i++ ) {
appendOne(targets[i], toAppend.map(a => a.cloneNode(true)));
}
appendOne(targets[limit], toAppend);
safe.uboLog(logPrefix, 'Node(s) appended');
if ( toRemove.length === 0 ) { return true; }
setTimeout(remove, duration);

View file

@ -51,6 +51,7 @@ function preventFetchFn(
const propNeedles = parsePropertiesToMatchFn(propsToMatch, 'url');
const validResponseProps = {
ok: [ false, true ],
status: [ 403 ],
statusText: [ '', 'Not Found' ],
type: [ 'basic', 'cors', 'default', 'error', 'opaque' ],
};

View file

@ -0,0 +1,270 @@
/*******************************************************************************
uBlock Origin - a comprehensive, efficient content blocker
Copyright (C) 2019-present Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
import {
generateContentFn,
matchObjectPropertiesFn,
parsePropertiesToMatchFn,
} from './utils.js';
import { proxyApplyFn } from './proxy-apply.js';
import { registerScriptlet } from './base.js';
import { safeSelf } from './safe-self.js';
// Externally added to the private namespace in which scriptlets execute.
/* global scriptletGlobals */
/******************************************************************************/
function preventXhrFn(
trusted = false,
propsToMatch = '',
directive = ''
) {
if ( typeof propsToMatch !== 'string' ) { return; }
const safe = safeSelf();
const scriptletName = trusted ? 'trusted-prevent-xhr' : 'prevent-xhr';
const logPrefix = safe.makeLogPrefix(scriptletName, propsToMatch, directive);
const xhrInstances = new WeakMap();
const propNeedles = parsePropertiesToMatchFn(propsToMatch, 'url');
const warOrigin = scriptletGlobals.warOrigin;
const safeDispatchEvent = (xhr, type) => {
try {
xhr.dispatchEvent(new Event(type));
} catch {
}
};
proxyApplyFn('XMLHttpRequest.prototype.open', function(context) {
const { thisArg, callArgs } = context;
xhrInstances.delete(thisArg);
const [ method, url, ...args ] = callArgs;
if ( warOrigin !== undefined && url.startsWith(warOrigin) ) {
return context.reflect();
}
const haystack = { method, url };
if ( propsToMatch === '' && directive === '' ) {
safe.uboLog(logPrefix, `Called: ${safe.JSON_stringify(haystack, null, 2)}`);
return context.reflect();
}
if ( matchObjectPropertiesFn(propNeedles, haystack) ) {
const xhrDetails = Object.assign(haystack, {
xhr: thisArg,
defer: args.length === 0 || !!args[0],
directive,
headers: {
'date': '',
'content-type': '',
'content-length': '',
},
url: haystack.url,
props: {
response: { value: '' },
responseText: { value: '' },
responseXML: { value: null },
},
});
xhrInstances.set(thisArg, xhrDetails);
}
return context.reflect();
});
proxyApplyFn('XMLHttpRequest.prototype.send', function(context) {
const { thisArg } = context;
const xhrDetails = xhrInstances.get(thisArg);
if ( xhrDetails === undefined ) {
return context.reflect();
}
xhrDetails.headers['date'] = (new Date()).toUTCString();
let xhrText = '';
switch ( thisArg.responseType ) {
case 'arraybuffer':
xhrDetails.props.response.value = new ArrayBuffer(0);
xhrDetails.headers['content-type'] = 'application/octet-stream';
break;
case 'blob':
xhrDetails.props.response.value = new Blob([]);
xhrDetails.headers['content-type'] = 'application/octet-stream';
break;
case 'document': {
const parser = new DOMParser();
const doc = parser.parseFromString('', 'text/html');
xhrDetails.props.response.value = doc;
xhrDetails.props.responseXML.value = doc;
xhrDetails.headers['content-type'] = 'text/html';
break;
}
case 'json':
xhrDetails.props.response.value = {};
xhrDetails.props.responseText.value = '{}';
xhrDetails.headers['content-type'] = 'application/json';
break;
default: {
if ( directive === '' ) { break; }
xhrText = generateContentFn(trusted, xhrDetails.directive);
if ( xhrText instanceof Promise ) {
xhrText = xhrText.then(text => {
xhrDetails.props.response.value = text;
xhrDetails.props.responseText.value = text;
});
} else {
xhrDetails.props.response.value = xhrText;
xhrDetails.props.responseText.value = xhrText;
}
xhrDetails.headers['content-type'] = 'text/plain';
break;
}
}
if ( xhrDetails.defer === false ) {
xhrDetails.headers['content-length'] = `${xhrDetails.props.response.value}`.length;
Object.defineProperties(xhrDetails.xhr, {
readyState: { value: 4 },
responseURL: { value: xhrDetails.url },
status: { value: 200 },
statusText: { value: 'OK' },
});
Object.defineProperties(xhrDetails.xhr, xhrDetails.props);
return;
}
Promise.resolve(xhrText).then(( ) => xhrDetails).then(details => {
Object.defineProperties(details.xhr, {
readyState: { value: 1, configurable: true },
responseURL: { value: xhrDetails.url },
});
safeDispatchEvent(details.xhr, 'readystatechange');
return details;
}).then(details => {
xhrDetails.headers['content-length'] = `${details.props.response.value}`.length;
Object.defineProperties(details.xhr, {
readyState: { value: 2, configurable: true },
status: { value: 200 },
statusText: { value: 'OK' },
});
safeDispatchEvent(details.xhr, 'readystatechange');
return details;
}).then(details => {
Object.defineProperties(details.xhr, {
readyState: { value: 3, configurable: true },
});
Object.defineProperties(details.xhr, details.props);
safeDispatchEvent(details.xhr, 'readystatechange');
return details;
}).then(details => {
Object.defineProperties(details.xhr, {
readyState: { value: 4 },
});
safeDispatchEvent(details.xhr, 'readystatechange');
safeDispatchEvent(details.xhr, 'load');
safeDispatchEvent(details.xhr, 'loadend');
safe.uboLog(logPrefix, `Prevented with response:\n${details.xhr.response}`);
});
});
proxyApplyFn('XMLHttpRequest.prototype.getResponseHeader', function(context) {
const { thisArg } = context;
const xhrDetails = xhrInstances.get(thisArg);
if ( xhrDetails === undefined || thisArg.readyState < thisArg.HEADERS_RECEIVED ) {
return context.reflect();
}
const headerName = `${context.callArgs[0]}`;
const value = xhrDetails.headers[headerName.toLowerCase()];
if ( value !== undefined && value !== '' ) { return value; }
return null;
});
proxyApplyFn('XMLHttpRequest.prototype.getAllResponseHeaders', function(context) {
const { thisArg } = context;
const xhrDetails = xhrInstances.get(thisArg);
if ( xhrDetails === undefined || thisArg.readyState < thisArg.HEADERS_RECEIVED ) {
return context.reflect();
}
const out = [];
for ( const [ name, value ] of Object.entries(xhrDetails.headers) ) {
if ( !value ) { continue; }
out.push(`${name}: ${value}`);
}
if ( out.length !== 0 ) { out.push(''); }
return out.join('\r\n');
});
}
registerScriptlet(preventXhrFn, {
name: 'prevent-xhr.fn',
dependencies: [
generateContentFn,
matchObjectPropertiesFn,
parsePropertiesToMatchFn,
proxyApplyFn,
safeSelf,
],
});
/******************************************************************************/
/**
* @scriptlet prevent-xhr
*
* @description
* Prevent a XMLHttpRequest-baesed request from being sent to a remote server.
*
* @param propsToMatch
* The fetch arguments to match for the prevention to be triggered. The
* untrusted flavor limits the realm of response to return to safe values.
*
* @param [responseBody]
* Optional. The reponse to return when the prevention occurs. The response
* must be a safe constant value.
*
* */
function preventXhr(...args) {
return preventXhrFn(false, ...args);
}
registerScriptlet(preventXhr, {
name: 'prevent-xhr.js',
aliases: [
'no-xhr-if.js',
],
dependencies: [
preventXhrFn,
],
});
/******************************************************************************/
/**
* @scriptlet trusted-prevent-xhr
*
* @description
* Prevent a XMLHttpRequest-based request from being sent to a remote server.
*
* @param propsToMatch
* The fetch arguments to match for the prevention to be triggered. The
* untrusted flavor limits the realm of response to return to safe values.
*
* @param [responseBody]
* Optional. The reponse to return when the prevention occurs. The trusted
* version allows arbitrary response.
*
* */
function trustedPreventXhr(...args) {
return preventXhrFn(true, ...args);
}
registerScriptlet(trustedPreventXhr, {
name: 'trusted-prevent-xhr.js',
dependencies: [
preventXhrFn,
],
});

View file

@ -32,12 +32,12 @@ import './prevent-dialog.js';
import './prevent-fetch.js';
import './prevent-innerHTML.js';
import './prevent-settimeout.js';
import './prevent-xhr.js';
import './replace-argument.js';
import './spoof-css.js';
import {
collateFetchArgumentsFn,
generateContentFn,
getExceptionTokenFn,
getRandomTokenFn,
matchObjectPropertiesFn,
@ -381,196 +381,6 @@ function replaceFetchResponseFn(
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'prevent-xhr.fn',
fn: preventXhrFn,
dependencies: [
'generate-content.fn',
'match-object-properties.fn',
'parse-properties-to-match.fn',
'safe-self.fn',
],
});
function preventXhrFn(
trusted = false,
propsToMatch = '',
directive = ''
) {
if ( typeof propsToMatch !== 'string' ) { return; }
const safe = safeSelf();
const scriptletName = trusted ? 'trusted-prevent-xhr' : 'prevent-xhr';
const logPrefix = safe.makeLogPrefix(scriptletName, propsToMatch, directive);
const xhrInstances = new WeakMap();
const propNeedles = parsePropertiesToMatchFn(propsToMatch, 'url');
const warOrigin = scriptletGlobals.warOrigin;
const safeDispatchEvent = (xhr, type) => {
try {
xhr.dispatchEvent(new Event(type));
} catch {
}
};
const XHRBefore = XMLHttpRequest.prototype;
self.XMLHttpRequest = class extends self.XMLHttpRequest {
open(method, url, ...args) {
xhrInstances.delete(this);
if ( warOrigin !== undefined && url.startsWith(warOrigin) ) {
return super.open(method, url, ...args);
}
const haystack = { method, url };
if ( propsToMatch === '' && directive === '' ) {
safe.uboLog(logPrefix, `Called: ${safe.JSON_stringify(haystack, null, 2)}`);
return super.open(method, url, ...args);
}
if ( matchObjectPropertiesFn(propNeedles, haystack) ) {
const xhrDetails = Object.assign(haystack, {
xhr: this,
defer: args.length === 0 || !!args[0],
directive,
headers: {
'date': '',
'content-type': '',
'content-length': '',
},
url: haystack.url,
props: {
response: { value: '' },
responseText: { value: '' },
responseXML: { value: null },
},
});
xhrInstances.set(this, xhrDetails);
}
return super.open(method, url, ...args);
}
send(...args) {
const xhrDetails = xhrInstances.get(this);
if ( xhrDetails === undefined ) {
return super.send(...args);
}
xhrDetails.headers['date'] = (new Date()).toUTCString();
let xhrText = '';
switch ( this.responseType ) {
case 'arraybuffer':
xhrDetails.props.response.value = new ArrayBuffer(0);
xhrDetails.headers['content-type'] = 'application/octet-stream';
break;
case 'blob':
xhrDetails.props.response.value = new Blob([]);
xhrDetails.headers['content-type'] = 'application/octet-stream';
break;
case 'document': {
const parser = new DOMParser();
const doc = parser.parseFromString('', 'text/html');
xhrDetails.props.response.value = doc;
xhrDetails.props.responseXML.value = doc;
xhrDetails.headers['content-type'] = 'text/html';
break;
}
case 'json':
xhrDetails.props.response.value = {};
xhrDetails.props.responseText.value = '{}';
xhrDetails.headers['content-type'] = 'application/json';
break;
default: {
if ( directive === '' ) { break; }
xhrText = generateContentFn(trusted, xhrDetails.directive);
if ( xhrText instanceof Promise ) {
xhrText = xhrText.then(text => {
xhrDetails.props.response.value = text;
xhrDetails.props.responseText.value = text;
});
} else {
xhrDetails.props.response.value = xhrText;
xhrDetails.props.responseText.value = xhrText;
}
xhrDetails.headers['content-type'] = 'text/plain';
break;
}
}
if ( xhrDetails.defer === false ) {
xhrDetails.headers['content-length'] = `${xhrDetails.props.response.value}`.length;
Object.defineProperties(xhrDetails.xhr, {
readyState: { value: 4 },
responseURL: { value: xhrDetails.url },
status: { value: 200 },
statusText: { value: 'OK' },
});
Object.defineProperties(xhrDetails.xhr, xhrDetails.props);
return;
}
Promise.resolve(xhrText).then(( ) => xhrDetails).then(details => {
Object.defineProperties(details.xhr, {
readyState: { value: 1, configurable: true },
responseURL: { value: xhrDetails.url },
});
safeDispatchEvent(details.xhr, 'readystatechange');
return details;
}).then(details => {
xhrDetails.headers['content-length'] = `${details.props.response.value}`.length;
Object.defineProperties(details.xhr, {
readyState: { value: 2, configurable: true },
status: { value: 200 },
statusText: { value: 'OK' },
});
safeDispatchEvent(details.xhr, 'readystatechange');
return details;
}).then(details => {
Object.defineProperties(details.xhr, {
readyState: { value: 3, configurable: true },
});
Object.defineProperties(details.xhr, details.props);
safeDispatchEvent(details.xhr, 'readystatechange');
return details;
}).then(details => {
Object.defineProperties(details.xhr, {
readyState: { value: 4 },
});
safeDispatchEvent(details.xhr, 'readystatechange');
safeDispatchEvent(details.xhr, 'load');
safeDispatchEvent(details.xhr, 'loadend');
safe.uboLog(logPrefix, `Prevented with response:\n${details.xhr.response}`);
});
}
getResponseHeader(headerName) {
const xhrDetails = xhrInstances.get(this);
if ( xhrDetails === undefined || this.readyState < this.HEADERS_RECEIVED ) {
return super.getResponseHeader(headerName);
}
const value = xhrDetails.headers[headerName.toLowerCase()];
if ( value !== undefined && value !== '' ) { return value; }
return null;
}
getAllResponseHeaders() {
const xhrDetails = xhrInstances.get(this);
if ( xhrDetails === undefined || this.readyState < this.HEADERS_RECEIVED ) {
return super.getAllResponseHeaders();
}
const out = [];
for ( const [ name, value ] of Object.entries(xhrDetails.headers) ) {
if ( !value ) { continue; }
out.push(`${name}: ${value}`);
}
if ( out.length !== 0 ) { out.push(''); }
return out.join('\r\n');
}
};
self.XMLHttpRequest.prototype.open.toString = function() {
return XHRBefore.open.toString();
};
self.XMLHttpRequest.prototype.send.toString = function() {
return XHRBefore.send.toString();
};
self.XMLHttpRequest.prototype.getResponseHeader.toString = function() {
return XHRBefore.getResponseHeader.toString();
};
self.XMLHttpRequest.prototype.getAllResponseHeaders.toString = function() {
return XHRBefore.getAllResponseHeaders.toString();
};
}
/*******************************************************************************
@ -992,22 +802,6 @@ function webrtcIf(
});
}
/******************************************************************************/
builtinScriptlets.push({
name: 'prevent-xhr.js',
aliases: [
'no-xhr-if.js',
],
fn: preventXhr,
dependencies: [
'prevent-xhr.fn',
],
});
function preventXhr(...args) {
return preventXhrFn(false, ...args);
}
/**
* @scriptlet prevent-window-open
*
@ -2034,7 +1828,7 @@ function trustedClickElement(
const pos2 = s2.indexOf('=');
const key = pos2 !== -1 ? s2.slice(0, pos2).trim() : s2;
const value = pos2 !== -1 ? s2.slice(pos2+1).trim() : '';
out.re = new RegExp(`^${this.escapeRegexChars(key)}=${this.escapeRegexChars(value)}`);
out.re = new RegExp(`^${safe.escapeRegexChars(key)}=${safe.escapeRegexChars(value)}`);
return out;
}).filter(details => details !== undefined);
const allCookies = assertions.some(o => o.type === 'cookie')
@ -2337,25 +2131,6 @@ function trustedSuppressNativeMethod(
});
}
/*******************************************************************************
*
* Trusted version of prevent-xhr(), which allows the use of an arbitrary
* string as response text.
*
* */
builtinScriptlets.push({
name: 'trusted-prevent-xhr.js',
requiresTrust: true,
fn: trustedPreventXhr,
dependencies: [
'prevent-xhr.fn',
],
});
function trustedPreventXhr(...args) {
return preventXhrFn(true, ...args);
}
/**
* @trustedScriptlet trusted-prevent-dom-bypass
*

View file

@ -220,6 +220,14 @@ export function generateContentFn(trusted, directive) {
warXHR.send();
}).catch(( ) => '');
}
if ( directive.startsWith('join:') ) {
const parts = directive.slice(7)
.split(directive.slice(5, 7))
.map(a => generateContentFn(trusted, a));
return parts.some(a => a instanceof Promise)
? Promise.all(parts).then(parts => parts.join(''))
: parts.join('');
}
if ( trusted ) {
return directive;
}

View file

@ -4758,6 +4758,7 @@ StaticNetFilteringEngine.prototype.dnrFromCompiled = function(op, context, ...ar
if ( rule.condition.resourceTypes === undefined ) {
if ( rule.condition.excludedResourceTypes === undefined ) {
rule.condition.resourceTypes = [
'image',
'main_frame',
'sub_frame',
'xmlhttprequest',
@ -4765,12 +4766,17 @@ StaticNetFilteringEngine.prototype.dnrFromCompiled = function(op, context, ...ar
}
}
// https://github.com/uBlockOrigin/uBOL-home/discussions/575
if ( rule.condition.urlFilter === undefined ) {
const { urlFilter } = rule.condition;
if ( urlFilter === undefined ) {
if ( rule.condition.regexFilter === undefined ) {
if ( paramName !== '' ) {
rule.condition.urlFilter = `^${paramName}=`;
}
}
} else if ( urlFilter.startsWith('||') ) {
if ( urlFilter.toLowerCase().includes(paramName.toLowerCase()) === false ) {
rule.condition.urlFilter = `${rule.condition.urlFilter}*^${paramName}=`;
}
}
if ( rule.__modifierAction === ALLOW_REALM ) {
dnrAddRuleError(rule, `Unsupported removeparam exception: ${rule.__modifierValue}`);