[mv3] Add support for custom DNR rules

This feature is hidden behind the "Developer mode" setting in
the dashboard. When "Developer mode" is enabled, a tab named
"Develop" will become available in the dashboard. This tab is
meant to contain tools for technical users.

At the moment, the "Develop" pane allows to create custom DNR
rules through a (CodeMirror-based) editor.

For the sake of convenience, the DNR rule must be entered in
YAML-like format. The format is not really full compliant YAML,
just YAML-like, and very strict in order to ensure the parser
stays simple enough.

Lines starting with `#` are comments and will be ignored by the
parser.

Any line which do not match the parser's expectation will be
marked as invalid, and the whole DNR rule containing such invalid
lines will be discarded.

There must not be empty lines inside a rule definition.

Each DNR rule must be separated with a `---` line, which is
known as a YAML document separator.

String values must not be quoted, otherwise the quotes will be
considered part of the value. There is one exception: `''` will
be parsed as "an empty string".

The editor will attempt to auto-complete known DNR keywords. That
feature will improve over time.

Though the parser will identify some errors, not all invalid DNR
rules are currently identified by the parser, and these will be
reported when the rules are registered through the DNR API. Better
identifying invalid DNR rules at edit time will improve over time.

The editor will report `regexFilter` values which are not
supported by the DNR engine on the current platform.

The editor reacts to instances of `regexFilter: ...` to report
whether a regex value is supported. This means you can test for
a regex value by using `# regexFilter: ...` so that you do not
have to create an actual DNR rules just for the sake of testing.

Custom DNR rules can be exported into a JSON file (a format
known by the DNR API as a "static ruleset").

JSON-based ruleset can be imported, the content will be converted
to YAML-like syntax.

The editor will attempt to convert to YAML pasted content which
can be JSON-parsed. It's possible to paste partially or wholly
JSON-based rulesets.

When disabling "Developer mode", all custom DNR rules will be
unregistered from the DNR API. The DNR rules content will be left
intact in such case. Existing DNR rules will be registered into
the DNR API when re-enabling "Developer mode".

Administrators can prevent "Developer mode" from being enabled
by adding `develop` token to `disabledFeatures` setting.

Related discussion:
https://github.com/uBlockOrigin/uBOL-home/discussions/323

The main motivation is to give list maintainers a tool to assist
with resolving filter issues. Custom DNR rules can assist in
crafting and validating filters meant to work with uBOL.

A secondary motivation is to provide technical users the ability
to further customize their content blocker.

More conveniences will be added over time, this is a first version.
This commit is contained in:
Raymond Hill 2025-05-29 09:06:02 -04:00
parent 4e2585545b
commit 9339a75952
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
89 changed files with 3700 additions and 74 deletions

View file

@ -2,13 +2,17 @@
run_options := $(filter-out $@,$(MAKECMDGOALS))
.PHONY: all clean cleanassets test lint chromium opera firefox npm dig \
mv3-chromium mv3-firefox mv3-edge mv3-safari \
mv3-chromium mv3-firefox mv3-edge mv3-safari ubol-codemirror \
compare maxcost medcost mincost modifiers record wasm
sources := ./dist/version $(shell find ./assets -type f) $(shell find ./src -type f)
platform := $(wildcard platform/*/*)
assets := dist/build/uAssets
mv3-sources := $(shell find ./src -type f) $(wildcard platform/mv3/*) $(shell find ./platform/mv3/extension -type f)
mv3-sources := \
$(shell find ./src -type f) \
$(wildcard platform/mv3/*) \
$(shell find ./platform/mv3/extension -name codemirror-ubol -prune -o -type f) \
platform/mv3/extension/lib/codemirror/codemirror-ubol/dist/cm6.bundle.ubol.min.js
mv3-data := $(shell find ./dist/build/mv3-data -type f)
mv3-edge-deps := $(wildcard platform/mv3/edge/*)
@ -69,25 +73,28 @@ dig-snfe: dig
dist/build/mv3-data:
mkdir -p dist/build/mv3-data
ubol-codemirror:
$(MAKE) -sC platform/mv3/extension/lib/codemirror/codemirror-ubol/ ubol.bundle
dist/build/uBOLite.chromium: tools/make-mv3.sh $(mv3-sources) $(platform) $(mv3-data) dist/build/mv3-data
tools/make-mv3.sh chromium
mv3-chromium: dist/build/uBOLite.chromium
mv3-chromium: ubol-codemirror dist/build/uBOLite.chromium
dist/build/uBOLite.firefox: tools/make-mv3.sh $(mv3-sources) $(platform) $(mv3-data) dist/build/mv3-data
tools/make-mv3.sh firefox
mv3-firefox: dist/build/uBOLite.firefox
mv3-firefox: ubol-codemirror dist/build/uBOLite.firefox
dist/build/uBOLite.edge: tools/make-mv3.sh $(mv3-sources) $(mv3-edge-deps) $(mv3-data) dist/build/mv3-data
tools/make-mv3.sh edge
mv3-edge: dist/build/uBOLite.edge
mv3-edge: ubol-codemirror dist/build/uBOLite.edge
dist/build/uBOLite.safari: tools/make-mv3.sh $(mv3-sources) $(mv3-safari-deps) $(mv3-data) dist/build/mv3-data
tools/make-mv3.sh safari
mv3-safari: dist/build/uBOLite.safari
mv3-safari: ubol-codemirror dist/build/uBOLite.safari
dist/build/uAssets:
tools/pull-assets.sh

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "سيتم حظر التنقل إلى المواقع غير المرغوب فيها، وسيتم تقديم خيار لك للمتابعة.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "البحث عن القوائم",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Пераход да патэнцыйна непажаданых сайтаў будзе заблакаваны, і вам будзе прапанавана магчымасць працягнуць.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Знайсці спісы",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Выйсці з рэжыму імгненнага хавання элементаў",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Навигацията към потенциално нежелани сайтове ще бъде блокирана и ще ви бъде предложена възможността да продължите.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Намиране на списъци",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Излизане от режима на временно скриване на елемента",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "সম্ভাব্য অযাচিত ওয়েবসাইট অবরুদ্ধ করা হবে, আর সেখানে আগানোর উপায় দেওয়া থাকবে।",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "তালিকা খুঁজো",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Stanket e vo ar merdeiñ etrezek lec'hiennoù a c'hallfe bezañ dañjerus, ha moaien a vo deoc'h dibab da genderc'hel pe get.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Kavout rolloù",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Kuitaat ar mod \"dilemel elfennoù\"",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Es blocarà la navegació en webs potencialment no desitjables, oferint-vos la possibilitat de continuar.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Cerca llistes",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Surt del mode d'eliminació d'elements",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigace na potenciálně nežádoucí stránky bude zablokována a bude vám nabídnuta možnost pokračovat.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Najít seznamy",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Opustit režim dočasného skrytí prvků",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigering til potentielt uønskede websteder blokeres, men man tilbydes muligheden for at fortsætte.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lister",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Forlad elementdræber­tilstand",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Die Navigation zu potenziell unerwünschten Websites wird verhindert, jedoch wird eine Möglichkeit zum Fortfahren angeboten.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Listen suchen",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Temporären Modus beenden",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Θα απετραπεί η πρόσβαση σε πιθανά ανεπιθύμητους ιστοτόπους. Θα σας προσφερθεί η επιλογή να συνεχίσετε.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Εύρεση λιστών",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Έξοδος από λειτουργία αφαίρεσης στοιχείων",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enables access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,29 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
},
"dnrRulesCountInfo": {
"message": "Number of registered rules: {count}",
"description": "Short sentence to report the number of currently registered DNR rules"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "La navegación a sitios potencialmente no deseados será bloqueada, y se te ofrecerá la opción de continuar.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Encontrar listas",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Salir del modo eliminación de elementos",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Kahtlastele veebilehtedele suunamist takistatakse ja pakutakse võimalust jätkamiseks.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Otsi nimekirju",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Välju elemendi hävitusrežiimist",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Irten elementua zapper moduan",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Potentiaalisesti ei-toivottujen sivustojen avaaminen estetään mahdollisuudella jatkaa.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Etsi listoja",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "La navigation vers des sites potentiellement indésirables sera bloquée, et vous aurez la possibilité de continuer",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Trouver des listes",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Quitter le mode Zappeur d'élément",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigaasje nei potinsjeel net-winske websites wurdt blokkearre, en jo krije de opsje om troch te gean.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Listen sykje",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Elemint­wisker­modus slute",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Vaise bloquear a navegación en webs potencialmente non desexables, e ofrecerase a opción de bloquealas.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Atopa listas",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Sair do modo eliminador de elementos",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "ניווט אפשרי לאתרים לא רצויים יחסם ותהיה אפשרות להחליט להמשיך.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "חיפוש רשימות",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigacija do potencijalno nepoželjnih stranica bit će blokirana i bit će vam ponuđena opcija za nastavak.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Pronađi liste",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Zatvori način rada uklanjanja elementa",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Az esetleges nem kívánatos webhelyekre való navigáció blokkolva lesz, és rákérdez arra, hogy folytatja-e.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Listák keresése",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Kilépés az elemeltávolító módból",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigasi ke situs yang mungkin tidak diinginkan akan diblokir, dan Anda akan ditawari opsi untuk melanjutkan.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Temukan daftar",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "La navigazione verso siti potenzialmente indesiderati verrà bloccata e ti verrà offerta la possibilità di procedere.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Trova elenchi",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Esci dalla modalità blocco rapido",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "望ましくない可能性のあるサイトへのナビゲーションはブロックされ、次に進むためのオプションが表示されます。",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "リストを検索",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "要素抹消モードを終了",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "შეიზღუდა სავარაუდოდ არასასურველ გვერდებზე გადასვლა, საშუალება გეძლევათ, მაინც განაგრძოთ.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "სიების მოძიება",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "ნაწილების ამოჭრის რეჟიმიდან გასვლა",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "잠재적으로 좋지 않은 사이트로의 접속을 차단하고, 사용자가 진행할지 선택하도록 합니다.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "목록 찾기",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "구성 요소 제거 모드 종료",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Iespējami nevēlamu vietņu apmeklēšana tiks aizturēta, un tiks piedāvāta iespēja turpināt.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Atrast sarakstus",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Iziet no elementu iznīcināšanas",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Најди листи",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigering til potensielt uønskede nettsteder blir blokkert, og du får tilbud om å fortsette.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Finn lister",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigatie naar mogelijk ongewenste websites wordt geblokkeerd, en u krijgt de mogelijkheid om door te gaan.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Lijsten zoeken",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Element­wisser­modus sluiten",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Nawigacja do potencjalnie niepożądanych witryn zostanie zablokowana i pojawi się opcja kontynuowania.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Znajdź listy",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Wyjdź z trybu usuwania elementów",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "A navegação até sites potencialmente indesejáveis será bloqueada e será oferecido a você a opção de prosseguir.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Achar listas",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Sair do modo do elemento zapper",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "A navegação para sites potencialmente indesejáveis será bloqueada e ser-lhe-á dada a opção de proceder.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Encontrar listas",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Sair do modo \"element zapper\"",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigarea către site-uri potențial nedorite va fi blocată și vi se va oferi opțiunea de a continua.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Căutați liste",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Переход к потенциально нежелательным сайтам будет заблокирован, и будет дана возможность продолжить переход на сайт",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Найти списки",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Покинуть режим временного скрытия элемента",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "අනවශ්‍ය විය හැකි අඩවි වෙත සංචාලනය අවහිර කරනු ලබන අතර, ඉදිරියට යාමට ඔබට විකල්පය ලබා දෙනු ඇත.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "ලැයිස්තු සොයන්න",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "මූලද්‍රව්‍ය zapper ප්‍රකාරයෙන් ඉවත් වන්න",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigácia na potenciálne nežiaduce stránky sa zablokuje a ponúkne sa vám možnosť pokračovať.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Nájsť zoznamy",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Ukončiť režim dočasného skrytia prvkov",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Uebsajtet potencialisht të padëshirueshme do të bllokohen dhe do t'ju jepet mundësia për të vazhduar.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Gjej listat",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Mbyll asgjësuesin e elementeve",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Навигација до потенцијално непожељних сајтова ће бити блокирана и биће вам понуђена опција да наставите.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Пронађи листе",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigering till potentiellt oönskade webbplatser kommer att blockeras och du kommer att erbjudas alternativet att fortsätta.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Hitta listor",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Lämna elementzapperläge",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "การนำทางไปยังเว็บไซต์ที่ไม่พึงประสงค์จะถูกบล็อก และคุณจะได้รับตัวเลือกเพื่อดำเนินการต่อ",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "หารายการ",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "İstenmeyebilecek sitelere erişim engellenecek ve devam etme seçeneği sunulacaktır.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Liste bul",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Öge silme modundan çık",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Перехід до потенційно небажаних сайтів буде заблоковано, та вам буде запропоновано можливість продовжити",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Знайти списки",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Вийти з режиму тимчасового приховування елементів",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Navigation to potentially undesirable sites will be blocked, and you will be offered the option to proceed.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Exit element zapper mode",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "Việc điều hướng đến các trang web có khả năng không mong muốn sẽ bị chặn và bạn sẽ được cung cấp tùy chọn để tiếp tục.",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "Tìm danh sách",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "Thoát khỏi chế độ chặn phần tử tạm thời",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "导航至潜在不良网站的操作将被拦截,并且您将可以选择继续前往。",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "查找列表",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "退出临时元素移除模式",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -233,8 +233,16 @@
},
"enableStrictBlockLegend": {
"message": "前往潛在不良網站的導航將被阻止,您可以選擇是否繼續前往。",
"description": "Short description for a checkbox in the options page"
},
"developerModeLabel": {
"message": "Developer mode",
"description": "Label for a checkbox in the options page"
},
"developerModeLegend": {
"message": "Enable access to features suitable for technical users.",
"description": "Short description for a checkbox in the options page"
},
"findListsPlaceholder": {
"message": "尋找清單",
"description": "Placeholder for the input field used to find lists"
@ -282,5 +290,25 @@
"zapperTipQuit": {
"message": "離開元素臨時移除模式",
"description": "Tooltip for the button used to exit zapper mode"
},
"saveButton": {
"message": "Save",
"description": "Text for buttons used to save changes"
},
"revertButton": {
"message": "Revert",
"description": "Text for buttons used to revert changes"
},
"importAndAppendButton": {
"message": "Import and append…",
"description": "Text for buttons used to import and append content"
},
"exportButton": {
"message": "Export…",
"description": "Text for buttons used to export content"
},
"dnrRulesSummary": {
"message": "Enter your own DNR rules below. <b>Do not add content from untrusted sources.</b>",
"description": "Short description of the DNR rules editor pane"
}
}

View file

@ -39,9 +39,15 @@ a {
color: var(--info3-ink);
fill: var(--info3-ink);
}
input[type="number"] {
width: 5em;
}
input[type="file"] {
display: none;
}
@media (max-height: 640px), (max-height: 800px) and (max-width: 480px) {
.body > p,
.body > ul {

View file

@ -1,4 +1,4 @@
#dashboard-nav {
nav {
background-color: var(--surface-1);
border: 0;
border-bottom: 1px solid var(--border-1);
@ -11,7 +11,7 @@
top: 0;
z-index: 100;
}
.tabButton {
nav > .tabButton {
background-color: transparent;
border: 0;
border-bottom: 3px solid transparent;
@ -24,29 +24,34 @@
text-decoration: none;
white-space: nowrap;
}
.tabButton:focus {
nav > .tabButton:focus {
outline: 0;
}
.tabButton:hover {
nav > .tabButton:hover {
background-color: var(--dashboard-tab-hover-surface);
border-bottom-color: var(--dashboard-tab-hover-border);
}
body[data-pane="settings"] #dashboard-nav .tabButton[data-pane="settings"],
body[data-pane="rulesets"] #dashboard-nav .tabButton[data-pane="rulesets"],
body[data-pane="develop"] #dashboard-nav .tabButton[data-pane="develop"],
body[data-pane="about"] #dashboard-nav .tabButton[data-pane="about"] {
background-color: var(--dashboard-tab-active-surface);
border-bottom: 3px solid var(--dashboard-tab-active-ink);
color: var(--dashboard-tab-active-ink);
fill: var(--dashboard-tab-active-ink);
}
body:not([data-develop="true"]) #dashboard-nav .tabButton[data-pane="develop"] {
display: none;
}
body > section {
display: none;
padding-bottom: 8rem;
padding-bottom: 2rem;
}
body[data-pane="settings"] > section[data-pane="settings"],
body[data-pane="rulesets"] > section[data-pane="rulesets"],
body[data-pane="develop"] > section[data-pane="develop"],
body[data-pane="about"] > section[data-pane="about"] {
display: block;
}
@ -59,10 +64,10 @@ body[data-pane="about"] > section[data-pane="about"] {
}
/* touch-screen devices */
:root.mobile #dashboard-nav {
:root.mobile nav {
flex-wrap: nowrap;
overflow-x: auto;
}
:root.mobile #dashboard-nav .logo {
:root.mobile nav .logo {
display: none;
}

View file

@ -0,0 +1,112 @@
body[data-pane="develop"] {
height: 100vh;
}
section[data-pane="develop"] {
display: none;
flex-grow: 1;
overflow: hidden;
}
section[data-pane="develop"] > div {
display: flex;
flex-direction: column;
height: 100%;
}
section[data-pane="develop"] > div > * {
margin-bottom: 0;
margin-top: 1em;
}
#cm-dnrRules {
flex-grow: 1;
font-size: var(--monospace-size);
overflow: hidden;
}
/* https://discuss.codemirror.net/t/how-to-set-max-height-of-the-editor/2882/2 */
#cm-dnrRules .cm-editor {
background-color: var(--surface-0);
height: 100%;
}
#cm-dnrRules .cm-editor .cm-line {
border-bottom: 1px dotted transparent;
border-top: 1px dotted transparent;
}
#cm-dnrRules .cm-editor .cm-line:has(.ͼ5) {
border-bottom: 1px dotted var(--border-1);
border-top: 1px dotted var(--border-1);
}
#cm-dnrRules .cm-editor .cm-line.badline:not(.cm-activeLine) {
background-color: color-mix(in srgb, var(--info3-ink) 15%, transparent 85%);
}
#cm-dnrRules .cm-editor .cm-line .badmark {
text-decoration: underline var(--cm-negative) wavy;
text-decoration-skip-ink: none;
}
#cm-dnrRules .cm-editor .cm-panel.cm-search {
display: flex;
flex-wrap: wrap;
font-family: sans-serif;
font-size: var(--font-size);
gap: 0.5em 1em;
padding: 0.5em 1.5em 0.5em 0.5em;
}
#cm-dnrRules .cm-editor .cm-panel.cm-search > * {
margin: 0;
}
#cm-dnrRules .cm-editor .cm-panel.cm-search .cm-textfield,
#cm-dnrRules .cm-editor .cm-panel.cm-search .cm-button,
#cm-dnrRules .cm-editor .cm-panel.cm-search label {
background-image: inherit;
border: inherit;
flex-grow: 0;
font-size: var(--button-font-size);
min-height: calc(var(--button-font-size) * 1.8);
}
#cm-dnrRules .cm-editor .cm-panel.info-panel {
display: flex;
flex-wrap: nowrap;
font-size: var(--font-size);
padding: var(--default-gap-xxsmall) var(--default-gap-xsmall);
}
#cm-dnrRules .cm-editor .cm-panel.info-panel .info {
flex-grow: 1;
overflow: auto;
}
#cm-dnrRules .cm-editor .cm-panel.info-panel .close {
cursor: default;
flex-shrink: 0;
padding-inline-start: 1em;
}
#cm-dnrRules .cm-editor .cm-panel.info-panel .close::after {
content: '\2715';
}
#cm-dnrRules .cm-editor .cm-panel.summary-panel {
background-color: color-mix(in srgb, var(--info1-ink) 15%, transparent 85%);
}
#cm-dnrRules .cm-editor .cm-panel.feedback-panel {
background-color: color-mix(in srgb, var(--info3-ink) 15%, transparent 85%);
white-space: pre;
max-height: 10cqh;
}
#cm-dnrRules .cm-editor .cm-gutterElement {
cursor: default;
user-select: none;
}
#cm-dnrRules .cm-editor .cm-tooltip .badmark-tooltip {
background-color: color-mix(in srgb, var(--info3-ink) 15%, transparent 85%);
padding: var(--default-gap-xxsmall) var(--default-gap-xsmall);
}

View file

@ -25,6 +25,9 @@ body[data-forbid~="filteringMode"] section[data-pane="settings"] > div:has(> h3[
body[data-forbid~="filteringMode"] section[data-pane="settings"] > div:has(> h3[data-i18n="filteringMode0Name"]) {
display: none;
}
body[data-forbid~="develop"] #developerMode {
display: none;
}
label:has(input[type="checkbox"][disabled]),
label:has(input[type="checkbox"][disabled]) + legend {

View file

@ -13,18 +13,20 @@
<link rel="stylesheet" href="css/dashboard-common.css">
<link rel="stylesheet" href="css/filtering-mode.css">
<link rel="stylesheet" href="css/settings.css">
<link rel="stylesheet" href="css/develop.css">
<link rel="shortcut icon" type="image/png" href="img/icon_64.png"/>
<link rel="icon" type="image/png" href="img/icon_64.png"/>
</head>
<body data-pane="settings" class="loading">
<!-- -------- -->
<div id="dashboard-nav">
<nav id="dashboard-nav">
<span class="logo"><img data-i18n-title="extName" src="img/ublock.svg" alt="uBO Lite"></span><!--
--><button class="tabButton" type="button" data-pane="settings" data-i18n="settingsPageName" tabindex="0"></button><!--
--><button class="tabButton" type="button" data-pane="rulesets" data-i18n="aboutFilterLists" tabindex="0"></button><!--
--><button class="tabButton" type="button" data-pane="develop" tabindex="0">Develop</button><!--
--><button class="tabButton" type="button" data-pane="about" data-i18n="aboutPageName" tabindex="0"></button>
</div>
</nav>
<!-- -------- -->
<section data-pane="settings">
<div>
@ -85,7 +87,7 @@
</label>
</div>
<p><label id="strictBlockMode" data-i18n="enableStrictBlockLabel"><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span>_</label><legend data-i18n="enableStrictBlockLegend"></legend>
<p id="developerMode" hidden><label><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span>Developer mode</label>
<p id="developerMode"><label data-i18n="developerModeLabel"><span class="input checkbox"><input type="checkbox"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span>_</label><legend data-i18n="developerModeLegend"></legend>
</div>
<div>
@ -103,6 +105,20 @@
<div id="lists"></div>
</section>
<!-- -------- -->
<section data-pane="develop">
<div>
<p>
<button id="dnrRulesApply" class="preferred iconified" type="button" disabled><span class="fa-icon">save</span><span data-i18n="saveButton">_</span><span class="hover"></span></button>
<button id="dnrRulesRevert" class="iconified" type="button" disabled><span class="fa-icon">undo</span><span data-i18n="revertButton">_</span><span class="hover"></span></button>
&emsp;
<button id="dnrRulesImport" class="iconified" type="button"><span class="fa-icon">download-alt</span><span data-i18n="importAndAppendButton">_</span><span class="hover"></span></button>
<button id="dnrRulesExport" class="iconified" type="button"><span class="fa-icon">upload-alt</span><span data-i18n="exportButton">_</span><span class="hover"></span></button><input type="file" accept="json/application">
</p>
<p data-i18n="dnrRulesSummary"></p>
<div id="cm-dnrRules"></div>
</div>
</section>
<!-- -------- -->
<section data-pane="about">
<div class="body">
<div id="aboutNameVer" class="li"></div>
@ -125,6 +141,7 @@
<div class="li"><span><a href="https://fontawesome.com/" target="_blank">FontAwesome font family</a> by <a href="https://github.com/davegandy">Dave Gandy</a></span></div>
<div class="li"><span><a href="https://github.com/mathiasbynens/punycode.js" target="_blank">Punycode.js</a> by <a href="https://github.com/mathiasbynens">Mathias Bynens</a></span></div>
<div class="li"><span><a href="https://flagpedia.net/" target="_blank">Flags of the World</a> by <a href="https://www.davidkrmela.com/">David Krmela</a></span></div>
<div class="li"><span><a href="https://codemirror.net/" target="_blank">CodeMirror 6</a> by <a href="https://github.com/marijnh">Marijn Haverbeke</a></span></div>
</div>
</div>
</section>
@ -155,12 +172,28 @@
</span>
</div>
</div>
<template class="summary-panel">
<div class="info-panel summary-panel">
<div class="info"></div>
</div>
</template>
<template class="feedback-panel">
<div class="info-panel feedback-panel">
<div class="info"></div>
<div class="close"></div>
</div>
</template>
<template class="badmark-tooltip">
<div class="badmark-tooltip">
</div>
</template>
<script src="lib/codemirror/cm6.bundle.ubol.min.js"></script>
<script src="js/theme.js" type="module"></script>
<script src="js/fa-icons.js" type="module"></script>
<script src="js/i18n.js" type="module"></script>
<script src="js/dashboard.js" type="module"></script>
<script src="js/settings.js" type="module"></script>
<script src="js/develop.js" type="module"></script>
</body>
</html>

View file

@ -59,6 +59,7 @@ import {
setStrictBlockMode,
updateDynamicRules,
updateSessionRules,
updateUserRules,
} from './ruleset-manager.js';
import {
@ -135,6 +136,17 @@ async function onPermissionsAdded(permissions) {
/******************************************************************************/
function setDeveloperMode(state) {
rulesetConfig.developerMode = state === true;
toggleDeveloperMode(rulesetConfig.developerMode);
return Promise.all([
updateUserRules(),
saveRulesetConfig(),
]);
}
/******************************************************************************/
function onMessage(request, sender, callback) {
// Does not require trusted origin.
@ -275,9 +287,7 @@ function onMessage(request, sender, callback) {
return true;
case 'setDeveloperMode':
rulesetConfig.developerMode = request.state;
toggleDeveloperMode(rulesetConfig.developerMode);
saveRulesetConfig().then(( ) => {
setDeveloperMode(request.state).then(( ) => {
callback();
});
return true;
@ -390,6 +400,12 @@ function onMessage(request, sender, callback) {
});
break;
case 'updateUserDnrRules':
updateUserRules().then(result => {
callback(result);
});
return true;
default:
break;
}
@ -472,8 +488,15 @@ async function startSession() {
}
}
// Required to ensure the up to date property is available when needed
adminReadEx('disabledFeatures');
// Required to ensure up to date properties are available when needed
adminReadEx('disabledFeatures').then(items => {
if ( Array.isArray(items) === false ) { return; }
if ( items.includes('develop') ) {
if ( rulesetConfig.developerMode ) {
setDeveloperMode(false);
}
}
});
}
/******************************************************************************/

View file

@ -19,6 +19,12 @@
Home: https://github.com/gorhill/uBlock
*/
import {
localRead,
localRemove,
localWrite,
} from './ext.js';
import { dom } from './dom.js';
import { runtime } from './ext.js';
@ -32,7 +38,18 @@ import { runtime } from './ext.js';
dom.attr('a', 'target', '_blank');
dom.on('#dashboard-nav', 'click', '.tabButton', ev => {
dom.body.dataset.pane = ev.target.dataset.pane;
const { pane } = ev.target.dataset;
dom.body.dataset.pane = pane;
if ( pane === 'settings' ) {
localRemove('activeDashboardPane');
} else {
localWrite('activeDashboardPane', pane);
}
});
localRead('activeDashboardPane').then(pane => {
if ( typeof pane !== 'string' ) { return; }
dom.body.dataset.pane = pane;
});
/******************************************************************************/

View file

@ -0,0 +1,687 @@
/*******************************************************************************
uBlock Origin Lite - a comprehensive, MV3-compliant 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
*/
import {
browser,
localRead,
localRemove,
localWrite,
sendMessage,
} from './ext.js';
import { dom, qs$ } from './dom.js';
import { rulesFromText, textFromRules } from './dnr-parser.js';
import { dnr } from './ext-compat.js';
import { i18n$ } from './i18n.js';
/******************************************************************************/
// Details of YAML document(s) intersecting with a text span. If the text span
// starts on a YAML document divider, the previous YAML document will be
// included. If the text span ends on a YAML document divider, the next YAML
// document will be included.
function snapToYamlDocument(doc, start, end) {
let yamlDocStart = doc.lineAt(start).number;
if ( reYamlDocSeparator.test(doc.line(yamlDocStart).text) ) {
if ( yamlDocStart > 1 ) {
yamlDocStart -= 1;
}
}
while ( yamlDocStart > 1 ) {
const line = doc.line(yamlDocStart);
if ( reYamlDocSeparator.test(line.text) ) { break; }
yamlDocStart -= 1;
}
const lastLine = doc.lines;
let yamlDocEnd = doc.lineAt(end).number;
if ( reYamlDocSeparator.test(doc.line(yamlDocEnd).text) ) {
if ( yamlDocEnd < lastLine ) {
yamlDocEnd += 1;
}
}
while ( yamlDocEnd < lastLine ) {
const line = doc.line(yamlDocEnd);
if ( reYamlDocSeparator.test(line.text) ) { break; }
yamlDocEnd += 1;
}
return { yamlDocStart, yamlDocEnd };
}
function addToModifiedRange(doc, start, end) {
const { yamlDocStart, yamlDocEnd } = snapToYamlDocument(doc, start, end);
if ( modifiedRange.start === -1 || yamlDocStart < modifiedRange.start ) {
modifiedRange.start = yamlDocStart;
}
if ( modifiedRange.end === -1 || yamlDocEnd > modifiedRange.end ) {
modifiedRange.end = yamlDocEnd;
}
}
const reYamlDocSeparator = /^(?:---|...)\s*$/;
const modifiedRange = { start: -1, end: -1 };
/******************************************************************************/
function rulesFromJSON(json) {
let content = json.trim();
if ( /^[[{]/.test(content) === false ) {
const match = /^[^[{]+/.exec(content);
if ( match === null ) { return; }
content = content.slice(match[0].length);
}
const firstChar = content.charAt(0);
const expectedLastChar = firstChar === '[' ? ']' : '}';
if ( content.at(-1) !== expectedLastChar ) {
const re = new RegExp(`\\${expectedLastChar}[^\\${expectedLastChar}]+$`);
const match = re.exec(content);
if ( match === null ) { return; }
content = content.slice(0, match.index+1);
}
if ( content.startsWith('{') && content.endsWith('}') ) {
content = `[${content}]`;
}
try {
const rules = JSON.parse(content);
if ( Array.isArray(rules) ) { return rules; }
}
catch {
}
}
/******************************************************************************/
function lineIndentAt(line) {
const match = /^(?: {2})*/.exec(line.text);
const indent = match !== null ? match[0].length : -1;
if ( indent === -1 || (indent & 1) !== 0 ) { return -1; }
return indent / 2;
}
function getScopeAt(from) {
const { doc } = cmRules.state;
const lineFrom = doc.lineAt(from);
let depth = lineIndentAt(lineFrom);
if ( depth === -1 ) { return; }
const text = lineFrom.text.trim();
if ( text.startsWith('#') ) { return; }
const path = [];
const pos = text.indexOf(':');
if ( pos !== -1 ) {
path.push(text.slice(0, pos+1));
}
let lineNo = lineFrom.number;
while ( depth > 0 && lineNo > 1 ) {
lineNo -= 1;
const lineBefore = doc.line(lineNo);
const text = lineBefore.text.trim();
if ( text.startsWith('#') ) { continue; }
if ( lineIndentAt(lineBefore) > (depth-1) ) { continue; }
const match = /^- ([^:]+:)/.exec(text);
if ( match !== null ) {
path.unshift(match[1]);
} else {
path.unshift(text);
}
depth -= 1;
}
return path.join('');
}
function getAutocompleteCandidates(from) {
const scope = getScopeAt(from);
switch ( scope ) {
case '':
return {
before: /^$/,
candidates: [
{ token: 'action:', after: '\n ' },
{ token: 'condition:', after: '\n ' },
{ token: 'priority:', after: ' ' },
{ token: '---', after: '\n' },
]
};
case 'action:':
return {
before: /^ {2}$/,
candidates: [
{ token: 'type:', after: ' ' },
{ token: 'redirect:', after: '\n ' },
{ token: 'requestHeaders:', after: '\n - header: ' },
{ token: 'responseHeaders:', after: '\n - header: ' },
],
};
case 'action:type:':
return {
before: /: $/,
candidates: [
{ token: 'block', after: '\n ' },
{ token: 'redirect', after: '\n ' },
{ token: 'allow', after: '\n ' },
{ token: 'modifyHeaders', after: '\n ' },
{ token: 'upgradeScheme', after: '\n ' },
{ token: 'allowAllRequest', after: '\n ' },
],
};
case 'action:redirect:':
return {
before: /^ {4}$/,
candidates: [
{ token: 'extensionPath:', after: ' ' },
{ token: 'regexSubstitution:', after: ' ' },
{ token: 'transform:', after: '\n ' },
{ token: 'url:', after: ' ' },
],
};
case 'action:redirect:transform:':
return {
before: /^ {6}$/,
candidates: [
{ token: 'fragment:', after: ' ' },
{ token: 'host:', after: ' ' },
{ token: 'path:', after: ' ' },
{ token: 'port:', after: ' ' },
{ token: 'query:', after: ' ' },
{ token: 'scheme:', after: ' ' },
{ token: 'queryTransform:', after: '\n ' },
],
};
case 'action:redirect:transform:queryTransform:':
return {
before: /^ {8}$/,
candidates: [
{ token: 'addOrReplaceParams:', after: '\n - ' },
{ token: 'removeParams:', after: '\n - ' },
],
};
case 'action:responseHeaders:':
return {
before: /^ {4}- $/,
candidates: [
{ token: 'header:', after: ' ' },
],
};
case 'action:responseHeaders:header:':
return {
before: /^ {6}$/,
candidates: [
{ token: 'operation:', after: ' ' },
{ token: 'value:', after: ' ' },
],
};
case 'action:responseHeaders:header:operation:':
return {
before: /: $/,
candidates: [
{ token: 'append', after: '\n value: ' },
{ token: 'set', after: '\n value: ' },
{ token: 'remove', after: '\n ' },
],
};
case 'condition:':
return {
before: /^ {2}$/,
candidates: [
{ token: 'domainType:', after: ' ' },
{ token: 'isUrlFilterCaseSensitive:', after: ' ' },
{ token: 'regexFilter:', after: ' ' },
{ token: 'urlFilter:', after: ' ' },
{ token: 'initiatorDomains:', after: '\n - ' },
{ token: 'excludedInitiatorDomains:', after: '\n - ' },
{ token: 'requestDomains:', after: '\n - ' },
{ token: 'excludedRequestDomains:', after: '\n - ' },
{ token: 'resourceTypes:', after: '\n - ' },
{ token: 'excludedResourceTypes:', after: '\n - ' },
{ token: 'requestMethods:', after: '\n - ' },
{ token: 'excludedRequestMethods:', after: '\n - ' },
{ token: 'responseHeaders:', after: '\n - ' },
{ token: 'excludedResponseHeaders:', after: '\n - ' },
],
};
case 'condition:domainType:':
return {
before: /: $/,
candidates: [
{ token: 'firstParty', after: '\n ' },
{ token: 'thirdParty', after: '\n ' },
],
};
case 'condition:isUrlFilterCaseSensitive:':
return {
before: /: $/,
candidates: [
{ token: 'true', after: '\n ' },
{ token: 'false', after: '\n ' },
],
};
case 'condition:requestMethods:':
case 'condition:excludedRequestMethods:':
return {
before: /^ {4}- $/,
candidates: [
{ token: 'connect', after: '\n - ' },
{ token: 'delete', after: '\n - ' },
{ token: 'get', after: '\n - ' },
{ token: 'head', after: '\n - ' },
{ token: 'options', after: '\n - ' },
{ token: 'patch', after: '\n - ' },
{ token: 'post', after: '\n - ' },
{ token: 'put', after: '\n - ' },
{ token: 'other', after: '\n ' },
],
};
case 'condition:resourceTypes:':
case 'condition:excludedResourceTypes:':
return {
before: /^ {4}- $/,
candidates: [
{ token: 'main_frame', after: '\n - ' },
{ token: 'sub_frame', after: '\n - ' },
{ token: 'stylesheet', after: '\n - ' },
{ token: 'script', after: '\n - ' },
{ token: 'image', after: '\n - ' },
{ token: 'font', after: '\n - ' },
{ token: 'object', after: '\n - ' },
{ token: 'xmlhttprequest', after: '\n - ' },
{ token: 'ping', after: '\n - ' },
{ token: 'csp_report', after: '\n - ' },
{ token: 'media', after: '\n - ' },
{ token: 'websocket', after: '\n - ' },
{ token: 'webtransport', after: '\n - ' },
{ token: 'webbundle', after: '\n - ' },
{ token: 'other', after: '\n ' },
],
};
}
}
function autoComplete(context) {
const match = context.matchBefore(/[\w-]*/);
if ( match === undefined ) { return null; }
const result = getAutocompleteCandidates(match.from);
if ( result === undefined ) { return null; }
if ( result.before !== undefined ) {
const { doc } = context.state;
const line = doc.lineAt(context.pos);
const before = doc.sliceString(line.from, match.from);
if ( result.before.test(before) === false ) { return null; }
}
const filtered = result.candidates.filter(e =>
e.token !== match.text || e.after !== '\n'
);
return {
from: match.from,
options: filtered.map(e => ({ label: e.token, apply: `${e.token}${e.after}` })),
validFor: /\w*/,
};
}
/******************************************************************************/
function setEditorText(text) {
if ( text === undefined ) { return; }
if ( text !== '' ) { text += '\n'; }
cmRules.dispatch({
changes: {
from: 0, to: cmRules.state.doc.length,
insert: text
},
});
}
function getEditorText() {
return cmRules.state.doc.toString();
}
/******************************************************************************/
function saveEditorText() {
const text = getEditorText().trim();
const promise = text.length !== 0
? localWrite('userDnrRules', text)
: localRemove('userDnrRules');
promise.then(( ) => {
lastSavedText = text;
updateView();
}).then(( ) =>
sendMessage({ what: 'updateUserDnrRules' })
).then(result => {
if ( result instanceof Object === false ) { return; }
updateFeedbackPanel(result);
});
}
/******************************************************************************/
async function validateRegexes(regexes) {
if ( regexes.length === 0 ) { return; }
const promises = regexes.map(regex => validateRegex(regex));
await Promise.all(promises);
for ( const regex of regexes ) {
const i = validatedRegexes.regexes.indexOf(regex);
if ( i === -1 ) { continue; }
const reason = validatedRegexes.results[i];
if ( reason === true ) { continue; }
const entries = self.cm6.findAll(cmRules,
`(?<=\\bregexFilter: )${RegExp.escape(regex)}`
);
for ( const entry of entries ) {
self.cm6.spanErrorAdd(cmRules, entry.from, entry.to, reason);
}
}
}
async function validateRegex(regex) {
const details = await dnr.isRegexSupported({ regex });
const result = details.isSupported || details.reason;
if ( validatedRegexes.regexes.length > 32 ) {
validatedRegexes.regexes.pop();
validatedRegexes.results.pop();
}
validatedRegexes.regexes.unshift(regex);
validatedRegexes.results.unshift(result);
}
const validatedRegexes = {
regexes: [],
results: [],
};
/******************************************************************************/
function updateView() {
const { doc } = cmRules.state;
const changed = doc.toString().trim() !==
lastSavedText.trim();
dom.attr('#dnrRulesApply', 'disabled', changed ? null : '');
dom.attr('#dnrRulesRevert', 'disabled', changed ? null : '');
const { start, end } = modifiedRange;
if ( start === -1 || end === -1 ) { return; }
modifiedRange.start = modifiedRange.end = -1;
self.cm6.lineErrorClear(cmRules, start, end);
self.cm6.spanErrorClear(cmRules, start, end);
const firstLine = doc.line(start);
const lastLine = doc.line(end);
const text = doc.sliceString(firstLine.from, lastLine.to);
const { bad } = rulesFromText(text);
if ( Array.isArray(bad) && bad.length !== 0 ) {
self.cm6.lineErrorAdd(cmRules, bad.map(i => i + start));
}
const entries = self.cm6.findAll(
cmRules,
'\\bregexFilter: (\\S+)',
firstLine.from,
lastLine.to
);
const regexes = [];
for ( const entry of entries ) {
const regex = entry.match[1];
const i = validatedRegexes.regexes.indexOf(regex);
if ( i !== -1 ) {
const reason = validatedRegexes.results[i];
if ( reason === true ) { continue; }
self.cm6.spanErrorAdd(cmRules, entry.from+13, entry.to, reason);
} else {
regexes.push(regex);
}
}
validateRegexes(regexes);
}
function updateViewAsync() {
if ( updateViewAsync.timer !== undefined ) { return; }
updateViewAsync.timer = self.setTimeout(( ) => {
updateViewAsync.timer = undefined;
updateView();
}, 71);
}
/******************************************************************************/
function updateSummaryPanel(info) {
self.cm6.showSummaryPanel(cmRules, {
template: '.summary-panel',
text: i18n$('dnrRulesCountInfo')
.replace('{count}', (info.userDnrRuleCount || 0).toLocaleString()),
});
}
browser.storage.onChanged.addListener((changes, area) => {
if ( area !== 'local' ) { return; }
const { userDnrRuleCount } = changes;
if ( userDnrRuleCount instanceof Object === false ) { return; }
const { newValue } = changes.userDnrRuleCount;
updateSummaryPanel({ userDnrRuleCount: newValue });
});
localRead('userDnrRuleCount').then(userDnrRuleCount => {
updateSummaryPanel({ userDnrRuleCount })
});
function updateFeedbackPanel(info) {
const errors = [];
if ( Array.isArray(info.errors) ) {
info.errors.forEach(e => errors.push(e));
}
const text = errors.join('\n');
self.cm6.showFeedbackPanel(cmRules, { template: '.feedback-panel', text });
}
/******************************************************************************/
function importRulesFromFile() {
const input = qs$('input[type="file"]');
input.onchange = ev => {
input.onchange = null;
const file = ev.target.files[0];
if ( file === undefined || file.name === '' ) { return; }
if ( file.type !== 'application/json' ) { return; }
const fr = new FileReader();
fr.onload = ( ) => {
if ( typeof fr.result !== 'string' ) { return; }
const rules = rulesFromJSON(fr.result);
if ( rules === undefined ) { return; }
const text = textFromRules(rules);
if ( text === undefined ) { return; }
const { doc } = cmRules.state;
const lastChars = doc.toString().trimEnd().slice(-4);
const lastLine = doc.line(doc.lines);
let from = lastLine.to;
let prepend = '';
if ( lastLine.text !== '' ) {
prepend = '\n';
} else {
from = lastLine.from;
}
if ( /(?:^|\n)---$/.test(lastChars) === false ) {
prepend = `${prepend}---\n`;
}
cmRules.dispatch({ changes: { from, insert: `${prepend}${text}` } });
cmRules.focus();
};
fr.readAsText(file);
};
// Reset to empty string, this will ensure a change event is properly
// triggered if the user pick a file, even if it's the same as the last
// one picked.
input.value = '';
input.click();
}
dom.on('#dnrRulesImport', 'click', importRulesFromFile);
/******************************************************************************/
function exportRulesToFile() {
const text = getEditorText();
const { rules } = rulesFromText(text);
if ( Array.isArray(rules) === false ) { return; }
let ruleId = 1;
for ( const rule of rules ) {
rule.id = ruleId++;
}
const filename = 'my-ubol-dnr-rules.json';
const a = document.createElement('a');
a.href = `data:application/json;charset=utf-8,${JSON.stringify(rules, null, 2)}`;
dom.attr(a, 'download', filename || '');
dom.attr(a, 'type', 'application/json');
a.click();
}
dom.on('#dnrRulesExport', 'click', exportRulesToFile);
/******************************************************************************/
function importRulesFromPaste(from, to) {
if ( from === undefined || to === undefined ) { return; }
// Paste position must match start of a line
const { doc } = cmRules.state;
const lineFrom = doc.lineAt(from);
if ( lineFrom.from !== from ) { return; }
// Paste position must match a rule boundary
if ( lineFrom.number !== 1 ) {
const lineBefore = doc.line(lineFrom.number-1);
if ( /^---\s*$/.test(lineBefore.text) === false ) { return; }
}
const pastedText = doc.sliceString(from, to);
const rules = rulesFromJSON(pastedText);
if ( rules === undefined ) { return; }
const yamlText = textFromRules(rules);
if ( yamlText === undefined ) { return; }
cmRules.dispatch({ changes: { from, to, insert: yamlText } });
}
/******************************************************************************/
function cmUpdateListener(info) {
if ( info.docChanged === false ) { return; }
const doc = info.state.doc;
info.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
addToModifiedRange(doc, fromB, toB);
});
for ( const transaction of info.transactions ) {
if ( transaction.isUserEvent('input.paste') === false ) { continue; }
if ( transaction.docChanged === false ) { continue; }
let from, to;
transaction.changes.iterChangedRanges((fromA, toA, fromB, toB) => {
if ( from === undefined || fromB < from ) { from = fromB; }
if ( to === undefined || toB > to ) { to = toB; }
});
importRulesFromPaste(from, to);
break;
}
updateViewAsync();
}
/******************************************************************************/
function gutterClick(view, info) {
const reSeparator = /^---\s*/;
const { doc } = view.state;
const lineFirst = doc.lineAt(info.from);
if ( lineFirst.text === '' ) { return false; }
let { from, to } = lineFirst;
if ( reSeparator.test(lineFirst.text) ) {
let lineNo = lineFirst.number + 1;
while ( lineNo < doc.lines ) {
const line = doc.line(lineNo);
if ( reSeparator.test(line.text) ) { break; }
to = line.to;
lineNo += 1;
}
}
view.dispatch({
selection: { anchor: from, head: to+1 }
});
view.focus();
return true;
}
/******************************************************************************/
function hoverTooltip(view, pos, side) {
const details = view.domAtPos(pos);
const textNode = details.node;
if ( textNode.nodeType !== 3 ) { return null; }
const { parentElement } = textNode;
const targetElement = parentElement.closest('[data-tooltip]');
if ( targetElement === null ) { return null; }
const tooltipText = targetElement.getAttribute('data-tooltip');
if ( Boolean(tooltipText) === false ) { return null; }
const start = pos - details.offset;
const end = start + textNode.nodeValue.length;
if ( start === pos && side < 0 || end === pos && side > 0 ) { return null; }
return {
above: true,
pos: start,
end,
create() {
const template = document.querySelector('.badmark-tooltip');
const fragment = template.content.cloneNode(true);
const dom = fragment.querySelector('.badmark-tooltip');
dom.textContent = tooltipText;
return { dom };
},
};
}
/******************************************************************************/
const cmRules = (( ) => {
return self.cm6.createEditorView({
dnrRules: true,
oneDark: dom.cl.has(':root', 'dark'),
updateListener: cmUpdateListener,
saveListener: ( ) => {
saveEditorText();
},
lineError: true,
spanError: true,
// https://codemirror.net/examples/autocompletion/
autocompletion: {
override: [ autoComplete ],
activateOnCompletion: ( ) => true,
},
gutterClick,
hoverTooltip,
}, qs$('#cm-dnrRules'));
})();
/******************************************************************************/
let lastSavedText = '';
localRead('userDnrRules').then(text => {
text ||= '';
setEditorText(text);
lastSavedText = text;
self.cm6.resetUndoRedo(cmRules);
dom.on('#dnrRulesApply', 'click', ( ) => {
saveEditorText();
});
dom.on('#dnrRulesRevert', 'click', ( ) => {
setEditorText(lastSavedText);
sendMessage({ what: 'updateUserDnrRules' });
});
});
/******************************************************************************/

View file

@ -0,0 +1,607 @@
/*******************************************************************************
uBlock Origin Lite - a comprehensive, MV3-compliant 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
*/
/******************************************************************************/
const validActionValues = [
'block',
'redirect',
'allow',
'upgradeScheme',
'modifyHeaders',
'allowAllRequests',
];
const validBoolValues = [
'false',
'true',
];
const validHeaderOpValues = [
'append',
'remove',
'set',
];
const validDomainTypeValues = [
'firstParty',
'thirdParty',
];
const validRequestMethodValues = [
'connect',
'delete',
'get',
'head',
'options',
'patch',
'post',
'put',
'other',
];
const validResourceTypeValues = [
'main_frame',
'sub_frame',
'stylesheet',
'script',
'image',
'font',
'object',
'xmlhttprequest',
'ping',
'csp_report',
'media',
'websocket',
'webtransport',
'webbundle',
'other',
];
/******************************************************************************/
function selectParser(scope, rule, node) {
const parser = perScopeParsers[scope.join('.')];
if ( parser === undefined ) { return false; }
return parser(scope, rule, node);
}
const perScopeParsers = {
'': function(scope, rule, node) {
const { key, val } = node;
switch ( key ) {
case 'action':
if ( val !== undefined ) { return false; }
rule.action = {};
scope.push('action');
break;
case 'condition':
if ( val !== undefined ) { return false; }
rule.condition = {};
scope.push('condition');
break;
case 'priority': {
const n = parseInt(val, 10);
if ( isNaN(n) || n <= 1 ) { return false; }
rule.priority = n;
break;
}
default:
return false;
}
return true;
},
'action': function(scope, rule, node) {
const { key, val } = node;
switch ( key ) {
case 'type':
if ( validActionValues.includes(val) === false ) { return false; }
rule.action.type = val;
break;
case 'redirect':
rule.action.redirect = {};
scope.push('redirect');
break;
case 'requestHeaders':
rule.action.requestHeaders = [];
scope.push('requestHeaders');
break;
case 'responseHeaders':
rule.action.responseHeaders = [];
scope.push('responseHeaders');
break;
default:
return false;
}
return true;
},
'action.redirect': function(scope, rule, node) {
const { key, val } = node;
switch ( key ) {
case 'extensionPath':
rule.action.redirect.extensionPath = val;
break;
case 'regexSubstitution':
rule.action.redirect.regexSubstitution = val;
break;
case 'transform':
rule.action.redirect.transform = {};
scope.push('transform');
break;
case 'url':
rule.action.redirect.url = val;
break;
default:
return false;
}
return true;
},
'action.redirect.transform': function(scope, rule, node) {
const { key, val } = node;
switch ( key ) {
case 'fragment':
case 'host':
case 'path':
case 'port':
case 'query':
case 'scheme': {
if ( val === undefined ) { return false; }
rule.action.redirect.transform[key] = val;
break;
}
case 'queryTransform':
rule.action.redirect.transform.queryTransform = {};
scope.push('queryTransform');
break;
default:
return false;
}
return true;
},
'action.redirect.transform.queryTransform': function(scope, rule, node) {
const { key, val } = node;
if ( val !== undefined ) { return false; }
switch ( key ) {
case 'addOrReplaceParams':
rule.action.redirect.transform.queryTransform.addOrReplaceParams = [];
scope.push('addOrReplaceParams');
break;
case 'removeParams':
rule.action.redirect.transform.queryTransform.removeParams = [];
scope.push('removeParams');
break;
default:
return false;
}
return true;
},
'action.redirect.transform.queryTransform.addOrReplaceParams': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
rule.action.redirect.transform.queryTransform.addOrReplaceParams.push({});
scope.push('@');
return selectParser(scope, rule, node);
},
'action.redirect.transform.queryTransform.addOrReplaceParams.@': function(scope, rule, node) {
const { key, val } = node;
if ( val === undefined ) { return false; }
const item = rule.action.redirect.transform.queryTransform.addOrReplaceParams.at(-1);
switch ( key ) {
case 'key':
item.key = val;
break;
case 'value':
item.value = val;
break;
case 'replaceOnly':
if ( validBoolValues.includes(val) === false ) { return false; }
item.replaceOnly = val === 'true';
break;
default:
return false;
}
return true;
},
'action.redirect.transform.queryTransform.removeParams': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
rule.action.redirect.transform.queryTransform.removeParams.push(node.val);
return true;
},
'action.requestHeaders': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
rule.action.requestHeaders.push({});
scope.push('@');
return selectParser(scope, rule, node);
},
'action.requestHeaders.@': function(scope, rule, node) {
const { key, val } = node;
const item = rule.action.requestHeaders.at(-1);
switch ( key ) {
case 'header':
item.header = val;
break;
case 'value':
item.value = val;
break;
case 'operation':
if ( validHeaderOpValues.includes(val) === false ) { return false; }
item.operation = val;
break;
default:
return false;
}
return true;
},
'action.responseHeaders': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
rule.action.responseHeaders.push({});
scope.push('@');
return selectParser(scope, rule, node);
},
'action.responseHeaders.@': function(scope, rule, node) {
const { key, val } = node;
const item = rule.action.responseHeaders.at(-1);
switch ( key ) {
case 'header':
item.header = val;
break;
case 'value':
item.value = val;
break;
case 'operation':
if ( validHeaderOpValues.includes(val) === false ) { return false; }
item.operation = val;
break;
default:
return false;
}
return true;
},
'condition': function(scope, rule, node) {
const { key, val } = node;
switch ( key ) {
case 'domainType':
if ( validDomainTypeValues.includes(val) === false ) { return false; }
rule.condition.domainType = val;
break;
case 'isUrlFilterCaseSensitive':
if ( validBoolValues.includes(val) === false ) { return false; }
rule.condition.isUrlFilterCaseSensitive = val === 'true';
break;
case 'regexFilter':
if ( val === undefined ) { return false; }
rule.condition.regexFilter = val;
break;
case 'urlFilter':
if ( val === undefined ) { return false; }
rule.condition.urlFilter = val;
break;
case 'initiatorDomains':
rule.condition.initiatorDomains = [];
scope.push('initiatorDomains');
break;
case 'excludedInitiatorDomains':
rule.condition.excludedInitiatorDomains = [];
scope.push('excludedInitiatorDomains');
break;
case 'requestDomains':
rule.condition.requestDomains = [];
scope.push('requestDomains');
break;
case 'excludedRequestDomains':
rule.condition.excludedRequestDomains = [];
scope.push('excludedRequestDomains');
break;
case 'resourceTypes':
rule.condition.resourceTypes = [];
scope.push('resourceTypes');
break;
case 'excludedResourceTypes':
rule.condition.excludedResourceTypes = [];
scope.push('excludedResourceTypes');
break;
case 'requestMethods':
rule.condition.requestMethods = [];
scope.push('requestMethods');
break;
case 'excludedRequestMethods':
rule.condition.excludedRequestMethods = [];
scope.push('excludedRequestMethods');
break;
case 'responseHeaders':
rule.condition.responseHeaders = [];
scope.push('responseHeaders');
break;
case 'excludedResponseHeaders':
rule.condition.excludedResponseHeaders = [];
scope.push('excludedResponseHeaders');
break;
default:
return false;
}
return true;
},
'condition.initiatorDomains': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
rule.condition.initiatorDomains.push(node.val);
return true;
},
'condition.excludedInitiatorDomains': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
rule.condition.excludedInitiatorDomains.push(node.val);
return true;
},
'condition.requestDomains': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
rule.condition.requestDomains.push(node.val);
return true;
},
'condition.excludedRequestDomains': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
rule.condition.excludedRequestDomains.push(node.val);
return true;
},
'condition.resourceTypes': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
if ( validResourceTypeValues.includes(node.val) === false ) { return false; }
rule.condition.resourceTypes.push(node.val);
return true;
},
'condition.excludedResourceTypes': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
if ( validResourceTypeValues.includes(node.val) === false ) { return false; }
rule.condition.excludedResourceTypes.push(node.val);
return true;
},
'condition.requestMethods': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
if ( validRequestMethodValues.includes(node.val) === false ) { return false; }
rule.condition.requestMethods.push(node.val);
return true;
},
'condition.excludedRequestMethods': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
if ( validRequestMethodValues.includes(node.val) === false ) { return false; }
rule.condition.excludedRequestMethods.push(node.val);
return true;
},
'condition.responseHeaders': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
rule.condition.responseHeaders.push({});
scope.push('@');
return selectParser(scope, rule, node);
},
'condition.responseHeaders.@': function(scope, rule, node) {
const item = rule.condition.responseHeaders.at(-1);
switch ( node.key ) {
case 'header':
if ( node.val === undefined ) { return false; }
item.header = node.val;
break;
case 'values':
item.values = [];
scope.push('values');
break;
case 'excludedValues':
item.excludedValues = [];
scope.push('excludedValues');
break;
default:
return false;
}
return true;
},
'condition.responseHeaders.@.values': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
const item = rule.condition.responseHeaders.at(-1);
item.values.push(node.val);
return true;
},
'condition.responseHeaders.@.excludedValues': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
const item = rule.condition.responseHeaders.at(-1);
item.excludedValues.push(node.val);
return true;
},
'condition.excludedResponseHeaders': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
rule.condition.excludedResponseHeaders.push({});
scope.push('@');
return selectParser(scope, rule, node);
},
'condition.excludedResponseHeaders.@': function(scope, rule, node) {
const item = rule.condition.excludedResponseHeaders.at(-1);
switch ( node.key ) {
case 'header':
if ( node.val === undefined ) { return false; }
item.header = node.val;
break;
case 'values':
item.values = [];
scope.push('values');
break;
case 'excludedValues':
item.excludedValues = [];
scope.push('excludedValues');
break;
default:
return false;
}
return true;
},
'condition.excludedResponseHeaders.@.values': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
const item = rule.condition.excludedResponseHeaders.at(-1);
item.values.push(node.val);
return true;
},
'condition.excludedResponseHeaders.@.excludedValues': function(scope, rule, node) {
if ( node.list !== true ) { return false; }
const item = rule.condition.excludedResponseHeaders.at(-1);
item.excludedValues.push(node.val);
return true;
},
};
/******************************************************************************/
function depthFromIndent(line) {
const match = /^\s*/.exec(line);
const count = match[0].length;
if ( (count & 1) !== 0 ) { return -1; }
return count / 2;
}
/******************************************************************************/
function nodeFromLine(line) {
const match = reNodeParser.exec(line);
const out = {};
if ( match === null ) { return out; }
if ( match[1] ) {
out.list = true;
}
if ( match[4] ) {
out.val = match[4].trim();
} else if ( match[3] ) {
out.key = match[2];
out.val = match[3].trim();
if ( out.val === "''" ) { out.val = '' };
} else {
out.key = match[2];
}
return out;
}
const reNodeParser = /^\s*(- )?(?:(\S+):( \S.*)?|(\S.*))$/;
/******************************************************************************/
function ruleFromLines(lines, indices) {
const rule = {};
const bad = [];
const scope = [];
for ( const i of indices ) {
const line = lines[i];
const depth = depthFromIndent(line);
if ( depth < 0 ) {
bad.push(i);
continue;
}
scope.length = depth;
const node = nodeFromLine(line);
const result = selectParser(scope, rule, node);
if ( result === false ) {
bad.push(i);
}
}
if ( bad.length !== 0 ) { return { bad }; }
return { rule };
}
/******************************************************************************/
export function rulesFromText(text) {
const rules = [];
const bad = [];
const lines = [ ...text.split(/\n\r|\r\n|\n|\r/), '---' ];
const indices = [];
for ( let i = 0; i < lines.length; i++ ) {
const line = lines[i].trimEnd();
const trimmed = line.trimStart();
if ( trimmed.startsWith('#') ) { continue; }
// Discard leading empty lines
if ( trimmed === '' ) {
if ( indices.length === 0 ) { continue; }
}
if ( line !== '---' && line !== '...' ) {
indices.push(i);
continue;
}
// Discard trailing empty lines
while ( indices.length !== 0 ) {
const s = lines[indices.at(-1)].trim();
if ( s.length !== 0 ) { break; }
indices.pop();
}
if ( indices.length === 0 ) { continue; }
const result = ruleFromLines(lines, indices);
if ( result.bad ) {
bad.push(...result.bad);
} else if ( result.rule ) {
rules.push(result.rule);
}
indices.length = 0;
}
return { rules, bad };
}
/******************************************************************************/
function textFromValue(val, depth) {
const indent = ' '.repeat(depth);
switch ( typeof val ) {
case 'boolean':
case 'number':
return `${val}`;
case 'string':
if ( val === '' ) { return "''"; }
return val;
}
const out = [];
if ( Array.isArray(val) ) {
for ( const a of val ) {
const s = textFromValue(a, depth+1);
if ( s === undefined ) { continue; }
out.push(`${indent}- ${s.trimStart()}`);
}
return out.join('\n');
}
if ( val instanceof Object ) {
for ( const [ a, b ] of Object.entries(val) ) {
const s = textFromValue(b, depth+1);
if ( s === undefined ) { continue; }
if ( b instanceof Object ) {
out.push(`${indent}${a}:\n${s}`);
} else {
out.push(`${indent}${a}: ${s}`);
}
}
return out.join('\n');
}
}
/******************************************************************************/
export function textFromRules(rules) {
if ( Array.isArray(rules) === false ) {
if ( rules instanceof Object === false ) { return; }
rules = [ rules ];
}
const out = [];
for ( const rule of rules ) {
if ( rule.id ) { rule.id = undefined };
const text = textFromValue(rule, 0);
if ( text === undefined ) { continue; }
out.push(text, '---' );
}
out.push('');
return out.join('\n');
}

View file

@ -36,7 +36,7 @@ const isSameRules = (a, b) => {
/******************************************************************************/
dnr.setAllowAllRules = async function(id, allowed, notAllowed, reverse) {
dnr.setAllowAllRules = async function(id, allowed, notAllowed, reverse, priority) {
const [
beforeDynamicRules,
beforeSessionRules,
@ -53,7 +53,7 @@ dnr.setAllowAllRules = async function(id, allowed, notAllowed, reverse) {
condition: {
resourceTypes: [ 'main_frame' ],
},
priority: 1000000,
priority,
};
if ( allowed.length ) {
rule0.condition.requestDomains = allowed.slice();
@ -69,7 +69,7 @@ dnr.setAllowAllRules = async function(id, allowed, notAllowed, reverse) {
condition: {
tabIds: [ webext.tabs.TAB_ID_NONE ],
},
priority: 1000000,
priority,
};
if ( allowed.length ) {
rule1.condition.initiatorDomains = allowed.slice();

View file

@ -35,11 +35,16 @@ import { dnr } from './ext-compat.js';
import { fetchJSON } from './fetch.js';
import { getAdminRulesets } from './admin.js';
import { hasBroadHostPermissions } from './utils.js';
import { rulesFromText } from './dnr-parser.js';
import { ubolLog } from './debug.js';
/******************************************************************************/
const SPECIAL_RULES_REALM = 5000000;
const USER_RULES_BASE_RULE_ID = 9000000;
const USER_RULES_PRIORITY = 1000000;
const TRUSTED_DIRECTIVE_BASE_RULE_ID = 8000000;
const TRUSTED_DIRECTIVE_PRIORITY = USER_RULES_PRIORITY + 1000000;
const STRICTBLOCK_PRIORITY = 29;
let dynamicRegexCount = 0;
@ -80,15 +85,12 @@ function getRulesetDetails() {
/******************************************************************************/
async function pruneInvalidRegexRules(realm, rulesIn) {
const rejectedRegexRules = [];
async function pruneInvalidRegexRules(realm, rulesIn, rejected = []) {
const validateRegex = regex => {
return dnr.isRegexSupported({ regex, isCaseSensitive: false }).then(result => {
const isSupported = result?.isSupported || false;
pruneInvalidRegexRules.validated.set(regex, isSupported);
if ( isSupported ) { return true; }
rejectedRegexRules.push(`\t${regex} ${result?.reason}`);
pruneInvalidRegexRules.validated.set(regex, result?.reason || true);
if ( result.isSupported ) { return true; }
rejected.push({ regex, reason: result?.reason });
return false;
});
};
@ -101,8 +103,11 @@ async function pruneInvalidRegexRules(realm, rulesIn) {
continue;
}
const { regexFilter } = rule.condition;
if ( pruneInvalidRegexRules.validated.has(regexFilter) ) {
toCheck.push(pruneInvalidRegexRules.validated.get(regexFilter));
const reason = pruneInvalidRegexRules.validated.get(regexFilter);
if ( reason !== undefined ) {
toCheck.push(reason === true);
if ( reason === true ) { continue; }
rejected.push({ regex: regexFilter, reason });
continue;
}
toCheck.push(validateRegex(regexFilter));
@ -111,9 +116,9 @@ async function pruneInvalidRegexRules(realm, rulesIn) {
// Collate results
const isValid = await Promise.all(toCheck);
if ( rejectedRegexRules.length !== 0 ) {
if ( rejected.length !== 0 ) {
ubolLog(`${realm} realm: rejected regexes:\n`,
rejectedRegexRules.join('\n')
rejected.map(e => `${e.regex}${e.reason}`).join('\n')
);
}
@ -126,6 +131,7 @@ pruneInvalidRegexRules.validated = new Map();
async function updateRegexRules(currentRules, addRules, removeRuleIds) {
// Remove existing regex-related block rules
for ( const rule of currentRules ) {
if ( rule.id >= SPECIAL_RULES_REALM ) { continue; }
const { type } = rule.action;
if ( type !== 'block' && type !== 'allow' ) { continue; }
if ( rule.condition.regexFilter === undefined ) { continue; }
@ -164,6 +170,7 @@ async function updateRegexRules(currentRules, addRules, removeRuleIds) {
async function updateRemoveparamRules(currentRules, addRules, removeRuleIds) {
// Remove existing removeparam-related rules
for ( const rule of currentRules ) {
if ( rule.id >= SPECIAL_RULES_REALM ) { continue; }
if ( rule.action.type !== 'redirect' ) { continue; }
if ( rule.action.redirect.transform === undefined ) { continue; }
removeRuleIds.push(rule.id);
@ -179,7 +186,6 @@ async function updateRemoveparamRules(currentRules, addRules, removeRuleIds) {
}
const removeparamRulesets = await Promise.all(toFetch);
// Removeparam rules can only be enforced with omnipotence
const allRules = [];
for ( const rules of removeparamRulesets ) {
if ( Array.isArray(rules) === false ) { continue; }
@ -201,6 +207,7 @@ async function updateRemoveparamRules(currentRules, addRules, removeRuleIds) {
async function updateRedirectRules(currentRules, addRules, removeRuleIds) {
// Remove existing redirect-related rules
for ( const rule of currentRules ) {
if ( rule.id >= SPECIAL_RULES_REALM ) { continue; }
if ( rule.action.type !== 'redirect' ) { continue; }
if ( rule.action.redirect.extensionPath === undefined ) { continue; }
removeRuleIds.push(rule.id);
@ -216,7 +223,6 @@ async function updateRedirectRules(currentRules, addRules, removeRuleIds) {
}
const redirectRulesets = await Promise.all(toFetch);
// Redirect rules can only be enforced with omnipotence
const allRules = [];
for ( const rules of redirectRulesets ) {
if ( Array.isArray(rules) === false ) { continue; }
@ -238,6 +244,7 @@ async function updateRedirectRules(currentRules, addRules, removeRuleIds) {
async function updateModifyHeadersRules(currentRules, addRules, removeRuleIds) {
// Remove existing header modification-related rules
for ( const rule of currentRules ) {
if ( rule.id >= SPECIAL_RULES_REALM ) { continue; }
if ( rule.action.type !== 'modifyHeaders' ) { continue; }
removeRuleIds.push(rule.id);
}
@ -252,7 +259,6 @@ async function updateModifyHeadersRules(currentRules, addRules, removeRuleIds) {
}
const rulesets = await Promise.all(toFetch);
// Redirect rules can only be enforced with omnipotence
const allRules = [];
for ( const rules of rulesets ) {
if ( Array.isArray(rules) === false ) { continue; }
@ -278,6 +284,7 @@ async function updateDynamicRules() {
// Remove potentially left-over strict-block rules from previous version
for ( const rule of currentRules ) {
if ( rule.id >= SPECIAL_RULES_REALM ) { continue; }
if ( isStrictBlockRule(rule) === false ) { continue; }
removeRuleIds.push(rule.id);
}
@ -294,7 +301,6 @@ async function updateDynamicRules() {
let ruleId = 1;
for ( const rule of addRules ) {
if ( rule?.condition.regexFilter ) { dynamicRegexCount += 1; }
if ( (rule.id || 0) >= TRUSTED_DIRECTIVE_BASE_RULE_ID ) { continue; }
rule.id = ruleId++;
}
if ( dynamicRegexCount !== 0 ) {
@ -467,7 +473,8 @@ async function filteringModesToDNR(modes) {
TRUSTED_DIRECTIVE_BASE_RULE_ID,
requestDomains.sort(),
excludedRequestDomains.sort(),
allowEverywhere
allowEverywhere,
TRUSTED_DIRECTIVE_PRIORITY
).then(modified => {
if ( modified === false ) { return; }
ubolLog(`${allowEverywhere ? 'Enabled' : 'Disabled'} DNR filtering for ${noneCount} sites`);
@ -662,6 +669,84 @@ async function getEnabledRulesetsDetails() {
/******************************************************************************/
async function getUserRules() {
const allRules = await dnr.getDynamicRules();
const userRules = [];
for ( const rule of allRules ) {
if ( rule.id < USER_RULES_BASE_RULE_ID ) { continue; }
userRules.push(rule);
}
return userRules;
}
async function updateUserRules() {
const [
userRules,
userRulesText = '',
] = await Promise.all([
getUserRules(),
localRead('userDnrRules'),
]);
const effectiveRulesText = rulesetConfig.developerMode
? userRulesText
: '';
const parsed = rulesFromText(effectiveRulesText);
const { rules } = parsed;
const removeRuleIds = [ ...userRules.map(a => a.id) ];
const rejectedRegexes = [];
const addRules = await pruneInvalidRegexRules('user', rules, rejectedRegexes);
const out = { added: 0, removed: 0, errors: [] };
if ( rejectedRegexes.length !== 0 ) {
rejectedRegexes.forEach(e =>
out.errors.push(`regexFilter: ${e.regex}${e.reason}`)
);
}
if ( removeRuleIds.length === 0 && addRules.length === 0 ) {
await localRemove('userDnrRuleCount');
return out;
}
let ruleId = 0;
for ( const rule of addRules ) {
rule.id = USER_RULES_BASE_RULE_ID + ruleId++;
rule.priority = (rule.priority || 1) + USER_RULES_PRIORITY;
}
// Rules are first removed separately to ensure registered rules match
// user rules text. A bad rule in user rules text would prevent the
// rules from being removed if the removal was done at the same time as
// adding rules.
try {
await dnr.updateDynamicRules({ removeRuleIds });
await dnr.updateDynamicRules({ addRules });
if ( removeRuleIds.length !== 0 ) {
ubolLog(`updateUserRules() / Removed ${removeRuleIds.length} dynamic DNR rules`);
}
if ( addRules.length !== 0 ) {
ubolLog(`updateUserRules() / Added ${addRules.length} DNR rules`);
}
out.added = addRules.length;
out.removed = removeRuleIds.length;
} catch(reason) {
console.info(`updateUserRules() / ${reason}`);
out.errors.push(`${reason}`);
} finally {
const userRules = await getUserRules();
if ( userRules.length === 0 ) {
await localRemove('userDnrRuleCount');
} else {
await localWrite('userDnrRuleCount', addRules.length);
}
}
return out;
}
/******************************************************************************/
export {
enableRulesets,
excludeFromStrictBlock,
@ -672,4 +757,5 @@ export {
setStrictBlockMode,
updateDynamicRules,
updateSessionRules,
updateUserRules,
};

View file

@ -29,17 +29,16 @@ import { renderFilterLists } from './filter-lists.js';
/******************************************************************************/
const cm6 = self.cm6;
const cmView = (( ) => {
const cmTrustedSites = (( ) => {
const options = {};
if ( dom.cl.has(':root', 'dark') ) {
options.oneDark = true;
}
options.placeholder = i18n$('noFilteringModePlaceholder');
return cm6.createEditorView(
cm6.createEditorState('', options),
qs$('#trustedSites')
);
return cm6.createEditorView(options, qs$('#trustedSites'));
})();
let cachedRulesetData = {};
/******************************************************************************/
@ -83,12 +82,10 @@ function renderWidgets() {
}
{
dom.prop('#developerMode input[type="checkbox"]', 'checked',
Boolean(cachedRulesetData.developerMode)
);
if ( cachedRulesetData.isSideloaded ) {
dom.attr('#developerMode', 'hidden', null);
}
const state = Boolean(cachedRulesetData.developerMode) &&
cachedRulesetData.disabledFeatures?.includes('develop') !== true;
dom.body.dataset.develop = `${state}`;
dom.prop('#developerMode input[type="checkbox"]', 'checked', state);
}
}
@ -171,10 +168,9 @@ dom.on('#strictBlockMode input[type="checkbox"]', 'change', ev => {
});
dom.on('#developerMode input[type="checkbox"]', 'change', ev => {
sendMessage({
what: 'setDeveloperMode',
state: ev.target.checked,
});
const state = ev.target.checked;
sendMessage({ what: 'setDeveloperMode', state });
dom.body.dataset.develop = `${state}`;
});
/******************************************************************************/
@ -183,9 +179,9 @@ function renderTrustedSites() {
const hostnames = cachedRulesetData.trustedSites || [];
let text = hostnames.map(hn => punycode.toUnicode(hn)).join('\n');
if ( text !== '' ) { text += '\n'; }
cmView.dispatch({
cmTrustedSites.dispatch({
changes: {
from: 0, to: cmView.state.doc.length,
from: 0, to: cmTrustedSites.state.doc.length,
insert: text
},
});
@ -202,7 +198,7 @@ function changeTrustedSites() {
}
function getStagedTrustedSites() {
const text = cmView.state.doc.toString();
const text = cmTrustedSites.state.doc.toString();
return text.split(/\s/).map(hn => {
try {
return punycode.toASCII(
@ -214,7 +210,7 @@ function getStagedTrustedSites() {
}).filter(hn => hn !== '');
}
dom.on(cmView.contentDOM, 'blur', changeTrustedSites);
dom.on(cmTrustedSites.contentDOM, 'blur', changeTrustedSites);
self.addEventListener('beforeunload', changeTrustedSites);

@ -1 +1 @@
Subproject commit 7067d0c2a9745bfddceedb46e1428a0b545954bd
Subproject commit 1d352d4489fea6f4aa10a32417eba4abd67db5c9

View file

@ -122,7 +122,7 @@ export const dnr = {
if ( optionsAfter === undefined ) { return; }
return nativeDNR.updateSessionRules(optionsAfter);
},
async setAllowAllRules(id, allowed, notAllowed, reverse) {
async setAllowAllRules(id, allowed, notAllowed, reverse, priority) {
const beforeRules = await this.getDynamicRules({ ruleIds: [ id+0 ] });
const addRules = [];
if ( reverse || allowed.length || notAllowed.length ) {
@ -130,7 +130,7 @@ export const dnr = {
id: id+0,
action: { type: 'allow' },
condition: { urlFilter: '*' },
priority: 1000000,
priority,
};
if ( allowed.length ) {
rule0.condition.domains = allowed;

View file

@ -37,6 +37,7 @@
--default-gap-small: 12px;
--default-gap-xsmall: 8px;
--default-gap-xxsmall: 4px;
--button-font-size: max(calc(var(--font-size) * 0.875), 14px);
}
/* Common uBO styles */
@ -85,7 +86,7 @@ button {
color: var(--button-ink);
display: inline-flex;
fill: var(--button-ink);
font-size: max(calc(var(--font-size) * 0.875), 14px);
font-size: var(--button-font-size);
justify-content: center;
min-height: 36px;
padding: 0 var(--font-size);

View file

@ -1,4 +1,13 @@
<!DOCTYPE html>
<!--
Requires a local server in root of uBlock repo:
python3 -m http.server
Then load the following URL in the browser:
http://localhost:8000/tools/jsonpath-tool.html
-->
<html>
<head>
<meta charset="utf-8">
@ -43,8 +52,39 @@ main {
<body>
<h2>uBO-flavored JSONPath tool</h2>
<main>
<textarea id="json-data" placeholder="JSON data" spellcheck="false"></textarea>
<input id="jsonpath-input" placeholder="JSON path expression" spellcheck="false" />
<textarea id="json-data" placeholder="JSON data" spellcheck="false">{
"store": {
"book": [
{ "category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{ "category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{ "category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{ "category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}</textarea>
<input id="jsonpath-input" placeholder="JSON path expression" spellcheck="false" value="$..book[?@.price<10]" />
<div id="jsonpath-result">&nbsp;</div>
</main>
@ -57,6 +97,18 @@ main {
import { JSONPath } from '../src/js/jsonpath.js';
function readJSON() {
const textarea = document.querySelector('#json-data');
try {
jsonData = JSON.parse(textarea.value);
} catch {
jsonData = {};
}
if ( typeof jsonData !== 'object' || jsonData === null ) {
jsonData = {};
}
}
function formatResult(a) {
if ( a === undefined ) { return 'undefined'; }
if ( jsonp.valid === false ) { return 'bad expression'; }
@ -85,13 +137,7 @@ main {
{
const textarea = document.querySelector('#json-data');
textarea.addEventListener('input', ( ) => {
try {
jsonData = JSON.parse(textarea.value);
} catch {
}
if ( typeof jsonData !== 'object' || jsonData === null ) {
jsonData = {};
}
readJSON();
process();
});
}
@ -102,6 +148,9 @@ main {
process();
});
}
readJSON();
process();
</script>
</body>

View file

@ -107,6 +107,8 @@ cp platform/mv3/extension/lib/codemirror/* \
"$UBOL_DIR"/lib/codemirror/ 2>/dev/null || :
cp platform/mv3/extension/lib/codemirror/codemirror-ubol/dist/cm6.bundle.ubol.min.js \
"$UBOL_DIR"/lib/codemirror/
cp platform/mv3/extension/lib/codemirror/codemirror-ubol/LICENSE \
"$UBOL_DIR"/lib/codemirror/
echo "*** uBOLite.mv3: Generating rulesets"
UBOL_BUILD_DIR=$(mktemp -d)