From 7388829aebeb1bc53d3831fd6547576eb748514d Mon Sep 17 00:00:00 2001 From: idk Date: Sat, 20 Aug 2022 19:50:55 -0400 Subject: [PATCH] don't check in other people's extensions Former-commit-id: 11f32c211b27863eb6891882999ef7c7101290e0 Former-commit-id: 661dd506d7288464a60b2cb80ab8b179a85cc0d5 --- build.xml | 16 +- .../scriptsafe.js/_locales/cs/messages.json | 858 + .../scriptsafe.js/_locales/de/messages.json | 858 + .../scriptsafe.js/_locales/en/messages.json | 858 + .../_locales/en_GB/messages.json | 858 + .../_locales/en_US/messages.json | 858 + .../scriptsafe.js/_locales/es/messages.json | 858 + .../scriptsafe.js/_locales/fr/messages.json | 858 + .../scriptsafe.js/_locales/hu/messages.json | 858 + .../scriptsafe.js/_locales/it/messages.json | 858 + .../scriptsafe.js/_locales/ja/messages.json | 858 + .../scriptsafe.js/_locales/ko/messages.json | 858 + .../scriptsafe.js/_locales/lv/messages.json | 858 + .../scriptsafe.js/_locales/nl/messages.json | 858 + .../scriptsafe.js/_locales/pl/messages.json | 850 + .../scriptsafe.js/_locales/ro/messages.json | 858 + .../scriptsafe.js/_locales/ru/messages.json | 858 + .../scriptsafe.js/_locales/sv/messages.json | 858 + .../_locales/zh_CN/messages.json | 859 + .../_locales/zh_TW/messages.json | 858 + .../_metadata/verified_contents.json | 1 + .../scriptsafe.js/css/bootstrap-theme.min.css | 6 + .../scriptsafe.js/css/bootstrap.min.css | 6 + .../extensions/scriptsafe.js/css/glyph.css | 1 + .../extensions/scriptsafe.js/css/options.css | 22 + .../extensions/scriptsafe.js/css/popup.css | 356 + .../extensions/scriptsafe.js/css/recents.css | 270 + .../fonts/glyphicons-halflings-regular.eot | Bin 0 -> 20127 bytes .../fonts/glyphicons-halflings-regular.svg | 288 + .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes .../extensions/scriptsafe.js/gpl.txt | 619 + .../scriptsafe.js/html/background.html | 13 + .../scriptsafe.js/html/options.html | 296 + .../extensions/scriptsafe.js/html/popup.html | 24 + .../scriptsafe.js/html/recents.html | 54 + .../scriptsafe.js/html/updated.html | 85 + .../scriptsafe.js/img/IconAllowed.png | Bin 0 -> 3529 bytes .../scriptsafe.js/img/IconDisabled.png | Bin 0 -> 3388 bytes .../scriptsafe.js/img/IconForbidden.png | Bin 0 -> 993 bytes .../extensions/scriptsafe.js/img/IconTemp.png | Bin 0 -> 3439 bytes .../extensions/scriptsafe.js/img/heartbig.png | Bin 0 -> 4899 bytes .../scriptsafe.js/img/heartsmall.png | Bin 0 -> 3661 bytes .../extensions/scriptsafe.js/img/icon128.png | Bin 0 -> 5903 bytes .../extensions/scriptsafe.js/img/icon16.png | Bin 0 -> 809 bytes .../extensions/scriptsafe.js/img/icon24.png | Bin 0 -> 1286 bytes .../extensions/scriptsafe.js/img/icon32.png | Bin 0 -> 1814 bytes .../extensions/scriptsafe.js/img/icon48.png | Bin 0 -> 3094 bytes .../scriptsafe.js/js/bootstrap.min.js | 7 + .../extensions/scriptsafe.js/js/common.js | 113 + .../extensions/scriptsafe.js/js/jquery.js | 4 + .../extensions/scriptsafe.js/js/options.js | 887 + .../extensions/scriptsafe.js/js/pako.js | 1 + .../extensions/scriptsafe.js/js/popup.js | 632 + .../extensions/scriptsafe.js/js/recents.js | 246 + .../extensions/scriptsafe.js/js/scriptsafe.js | 1583 ++ .../extensions/scriptsafe.js/js/ss.js | 848 + .../extensions/scriptsafe.js/js/updated.js | 23 + .../extensions/scriptsafe.js/js/webrtctest.js | 15 + .../scriptsafe.js/js/yoyo.js.REMOVED.git-id | 1 + .../extensions/scriptsafe.js/manifest.json | 53 + .../extensions/ublock.js/1p-filters.html | 70 + .../extensions/ublock.js/3p-filters.html | 87 + .../extensions/ublock.js/LICENSE.txt | 674 + .../ublock.js/_locales/ar/messages.json | 1254 ++ .../ublock.js/_locales/az/messages.json | 1254 ++ .../ublock.js/_locales/bg/messages.json | 1254 ++ .../ublock.js/_locales/bn/messages.json | 1254 ++ .../ublock.js/_locales/bs/messages.json | 1254 ++ .../ublock.js/_locales/ca/messages.json | 1254 ++ .../ublock.js/_locales/cs/messages.json | 1254 ++ .../ublock.js/_locales/cv/messages.json | 1254 ++ .../ublock.js/_locales/da/messages.json | 1254 ++ .../ublock.js/_locales/de/messages.json | 1254 ++ .../ublock.js/_locales/el/messages.json | 1254 ++ .../ublock.js/_locales/en/messages.json | 1256 ++ .../ublock.js/_locales/en_GB/messages.json | 1254 ++ .../ublock.js/_locales/eo/messages.json | 1254 ++ .../ublock.js/_locales/es/messages.json | 1254 ++ .../ublock.js/_locales/et/messages.json | 1254 ++ .../ublock.js/_locales/eu/messages.json | 1254 ++ .../ublock.js/_locales/fa/messages.json | 1254 ++ .../ublock.js/_locales/fi/messages.json | 1254 ++ .../ublock.js/_locales/fil/messages.json | 1254 ++ .../ublock.js/_locales/fr/messages.json | 1254 ++ .../ublock.js/_locales/fy/messages.json | 1254 ++ .../ublock.js/_locales/gl/messages.json | 1254 ++ .../ublock.js/_locales/he/messages.json | 1254 ++ .../ublock.js/_locales/hi/messages.json | 1254 ++ .../ublock.js/_locales/hr/messages.json | 1254 ++ .../ublock.js/_locales/hu/messages.json | 1254 ++ .../ublock.js/_locales/hy/messages.json | 1254 ++ .../ublock.js/_locales/id/messages.json | 1254 ++ .../ublock.js/_locales/it/messages.json | 1254 ++ .../ublock.js/_locales/ja/messages.json | 1254 ++ .../ublock.js/_locales/ka/messages.json | 1254 ++ .../ublock.js/_locales/kk/messages.json | 1254 ++ .../ublock.js/_locales/kn/messages.json | 1254 ++ .../ublock.js/_locales/ko/messages.json | 1254 ++ .../ublock.js/_locales/lt/messages.json | 1254 ++ .../ublock.js/_locales/lv/messages.json | 1254 ++ .../ublock.js/_locales/ml/messages.json | 1254 ++ .../ublock.js/_locales/mr/messages.json | 1254 ++ .../ublock.js/_locales/ms/messages.json | 1254 ++ .../ublock.js/_locales/nb/messages.json | 1254 ++ .../ublock.js/_locales/nl/messages.json | 1254 ++ .../ublock.js/_locales/no/messages.json | 1254 ++ .../ublock.js/_locales/oc/messages.json | 1254 ++ .../ublock.js/_locales/pl/messages.json | 1254 ++ .../ublock.js/_locales/pt_BR/messages.json | 1254 ++ .../ublock.js/_locales/pt_PT/messages.json | 1254 ++ .../ublock.js/_locales/ro/messages.json | 1254 ++ .../ublock.js/_locales/ru/messages.json | 1254 ++ .../ublock.js/_locales/sk/messages.json | 1254 ++ .../ublock.js/_locales/sl/messages.json | 1254 ++ .../ublock.js/_locales/so/messages.json | 1254 ++ .../ublock.js/_locales/sq/messages.json | 1254 ++ .../ublock.js/_locales/sr/messages.json | 1254 ++ .../ublock.js/_locales/sv/messages.json | 1254 ++ .../ublock.js/_locales/ta/messages.json | 1254 ++ .../ublock.js/_locales/te/messages.json | 1254 ++ .../ublock.js/_locales/th/messages.json | 1254 ++ .../ublock.js/_locales/tr/messages.json | 1254 ++ .../ublock.js/_locales/uk/messages.json | 1254 ++ .../ublock.js/_locales/ur/messages.json | 1254 ++ .../ublock.js/_locales/vi/messages.json | 1254 ++ .../ublock.js/_locales/zh_CN/messages.json | 1254 ++ .../ublock.js/_locales/zh_TW/messages.json | 1254 ++ .../_metadata/verified_contents.json | 1 + .../extensions/ublock.js/about.html | 59 + .../ublock.js/advanced-settings.html | 42 + .../extensions/ublock.js/asset-viewer.html | 51 + .../extensions/ublock.js/assets/assets.json | 704 + .../ublock.js/assets/resources/scriptlets.js | 1668 ++ .../easylist.txt.REMOVED.git-id | 1 + .../easyprivacy.txt.REMOVED.git-id | 1 + .../thirdparties/pgl.yoyo.org/as/README.md | 29 + .../thirdparties/pgl.yoyo.org/as/serverlist | 3653 ++++ .../list/effective_tld_names.dat | 14054 ++++++++++++++++ .../thirdparties/urlhaus-filter/LICENSE.md | 42 + .../urlhaus-filter/urlhaus-filter-online.txt | 5081 ++++++ .../ublock.js/assets/ublock/badlists.txt | 34 + .../ublock.js/assets/ublock/badware.txt | 1573 ++ .../ublock.js/assets/ublock/filters-2020.txt | 6164 +++++++ .../ublock/filters-2021.txt.REMOVED.git-id | 1 + .../ublock.js/assets/ublock/filters-2022.txt | 3570 ++++ .../assets/ublock/filters.txt.REMOVED.git-id | 1 + .../ublock.js/assets/ublock/lan-block.txt | 89 + .../ublock.js/assets/ublock/legacy.txt | 148 + .../ublock.js/assets/ublock/privacy.txt | 402 + .../ublock.js/assets/ublock/quick-fixes.txt | 183 + .../assets/ublock/resource-abuse.txt | 142 + .../ublock.js/assets/ublock/unbreak.txt | 4595 +++++ .../extensions/ublock.js/background.html | 12 + .../extensions/ublock.js/cloud-ui.html | 23 + .../extensions/ublock.js/css/1p-filters.css | 23 + .../extensions/ublock.js/css/3p-filters.css | 192 + .../extensions/ublock.js/css/about.css | 3 + .../ublock.js/css/advanced-settings.css | 26 + .../extensions/ublock.js/css/asset-viewer.css | 79 + .../extensions/ublock.js/css/click2load.css | 53 + .../extensions/ublock.js/css/cloud-ui.css | 104 + .../extensions/ublock.js/css/codemirror.css | 241 + .../extensions/ublock.js/css/common.css | 289 + .../ublock.js/css/dashboard-common.css | 55 + .../extensions/ublock.js/css/dashboard.css | 115 + .../extensions/ublock.js/css/devtools.css | 22 + .../ublock.js/css/document-blocked.css | 135 + .../extensions/ublock.js/css/dyna-rules.css | 79 + .../extensions/ublock.js/css/epicker-ui.css | 266 + .../extensions/ublock.js/css/fa-icons.css | 132 + .../css/fonts/Inter/Inter-Regular.woff2 | Bin 0 -> 100368 bytes .../css/fonts/Inter/Inter-SemiBold.woff2 | Bin 0 -> 106916 bytes .../ublock.js/css/fonts/Inter/LICENSE.txt | 93 + .../fonts/Metropolis/Metropolis-Regular.woff2 | Bin 0 -> 24152 bytes .../Metropolis/Metropolis-SemiBold.woff2 | Bin 0 -> 26564 bytes .../ublock.js/css/fonts/Metropolis/README.md | 25 + .../ublock.js/css/fonts/Metropolis/UNLICENSE | 24 + .../ublock.js/css/logger-ui-inspector.css | 116 + .../extensions/ublock.js/css/logger-ui.css | 948 ++ .../extensions/ublock.js/css/popup-fenix.css | 692 + .../extensions/ublock.js/css/settings.css | 74 + .../extensions/ublock.js/css/shortcuts.css | 55 + .../extensions/ublock.js/css/support.css | 71 + .../ublock.js/css/themes/default.css | 515 + .../extensions/ublock.js/css/whitelist.css | 22 + .../extensions/ublock.js/dashboard.html | 47 + .../extensions/ublock.js/devtools.html | 56 + .../ublock.js/document-blocked.html | 68 + .../extensions/ublock.js/dyna-rules.html | 67 + .../extensions/ublock.js/img/cloud.png | Bin 0 -> 5017 bytes .../ublock.js/img/fontawesome/LICENSE.txt | 25 + .../img/fontawesome/fontawesome-defs.svg | 79 + .../extensions/ublock.js/img/help16.png | Bin 0 -> 215 bytes .../extensions/ublock.js/img/icon_128.png | Bin 0 -> 3552 bytes .../extensions/ublock.js/img/icon_16-off.png | Bin 0 -> 552 bytes .../extensions/ublock.js/img/icon_16.png | Bin 0 -> 420 bytes .../extensions/ublock.js/img/icon_32-off.png | Bin 0 -> 1114 bytes .../extensions/ublock.js/img/icon_32.png | Bin 0 -> 1278 bytes .../extensions/ublock.js/img/icon_64.png | Bin 0 -> 2655 bytes .../ublock.js/img/material-design.svg | 16 + .../extensions/ublock.js/img/photon.svg | 16 + .../extensions/ublock.js/img/ublock-defs.svg | 27 + .../extensions/ublock.js/img/ublock.svg | 44 + .../ublock.js/is-webrtc-supported.html | 9 + .../extensions/ublock.js/js/1p-filters.js | 354 + .../extensions/ublock.js/js/3p-filters.js | 717 + .../extensions/ublock.js/js/about.js | 34 + .../ublock.js/js/advanced-settings.js | 211 + .../extensions/ublock.js/js/asset-viewer.js | 108 + .../extensions/ublock.js/js/assets.js | 1062 ++ .../extensions/ublock.js/js/background.js | 364 + .../extensions/ublock.js/js/base64-custom.js | 246 + .../extensions/ublock.js/js/benchmarks.js | 401 + .../extensions/ublock.js/js/biditrie.js | 932 + .../extensions/ublock.js/js/cachestorage.js | 489 + .../extensions/ublock.js/js/click2load.js | 60 + .../extensions/ublock.js/js/cloud-ui.js | 237 + .../ublock.js/js/codemirror/search-thread.js | 203 + .../ublock.js/js/codemirror/search.js | 452 + .../js/codemirror/ubo-dynamic-filtering.js | 239 + .../js/codemirror/ubo-static-filtering.js | 867 + .../extensions/ublock.js/js/commands.js | 217 + .../extensions/ublock.js/js/console.js | 59 + .../ublock.js/js/contentscript-extra.js | 569 + .../extensions/ublock.js/js/contentscript.js | 1373 ++ .../extensions/ublock.js/js/contextmenu.js | 230 + .../ublock.js/js/cosmetic-filtering.js | 1164 ++ .../ublock.js/js/dashboard-common.js | 215 + .../extensions/ublock.js/js/dashboard.js | 155 + .../extensions/ublock.js/js/devtools.js | 173 + .../ublock.js/js/document-blocked.js | 256 + .../extensions/ublock.js/js/dyna-rules.js | 680 + .../ublock.js/js/dynamic-net-filtering.js | 484 + .../extensions/ublock.js/js/epicker-ui.js | 926 + .../extensions/ublock.js/js/fa-icons.js | 126 + .../ublock.js/js/filtering-context.js | 391 + .../ublock.js/js/filtering-engines.js | 50 + .../extensions/ublock.js/js/hnswitches.js | 289 + .../extensions/ublock.js/js/hntrie.js | 778 + .../extensions/ublock.js/js/html-filtering.js | 455 + .../ublock.js/js/httpheader-filtering.js | 230 + .../extensions/ublock.js/js/i18n.js | 292 + .../ublock.js/js/is-webrtc-supported.js | 52 + .../ublock.js/js/logger-ui-inspector.js | 679 + .../extensions/ublock.js/js/logger-ui.js | 2893 ++++ .../extensions/ublock.js/js/logger.js | 88 + .../extensions/ublock.js/js/lz4.js | 205 + .../extensions/ublock.js/js/messaging.js | 1934 +++ .../extensions/ublock.js/js/pagestore.js | 1159 ++ .../extensions/ublock.js/js/popup-fenix.js | 1447 ++ .../ublock.js/js/redirect-engine.js | 617 + .../ublock.js/js/reverselookup-worker.js | 301 + .../extensions/ublock.js/js/reverselookup.js | 213 + .../ublock.js/js/scriptlet-filtering.js | 455 + .../js/scriptlets/cosmetic-logger.js | 386 + .../ublock.js/js/scriptlets/cosmetic-off.js | 48 + .../ublock.js/js/scriptlets/cosmetic-on.js | 48 + .../ublock.js/js/scriptlets/dom-inspector.js | 1008 ++ .../js/scriptlets/dom-survey-elements.js | 72 + .../js/scriptlets/dom-survey-scripts.js | 126 + .../ublock.js/js/scriptlets/epicker.js | 1340 ++ .../ublock.js/js/scriptlets/load-3p-css.js | 67 + .../js/scriptlets/load-large-media-all.js | 62 + .../load-large-media-interactive.js | 299 + .../ublock.js/js/scriptlets/noscript-spoof.js | 90 + .../scriptlets/should-inject-contentscript.js | 40 + .../ublock.js/js/scriptlets/subscriber.js | 113 + .../extensions/ublock.js/js/settings.js | 310 + .../extensions/ublock.js/js/shortcuts.js | 230 + .../extensions/ublock.js/js/start.js | 515 + .../ublock.js/js/static-ext-filtering-db.js | 224 + .../ublock.js/js/static-ext-filtering.js | 187 + .../ublock.js/js/static-filtering-io.js | 139 + .../ublock.js/js/static-filtering-parser.js | 3015 ++++ .../ublock.js/js/static-net-filtering.js | 4693 ++++++ .../extensions/ublock.js/js/storage.js | 1667 ++ .../extensions/ublock.js/js/support.js | 290 + .../extensions/ublock.js/js/tab.js | 1178 ++ .../extensions/ublock.js/js/tasks.js | 42 + .../extensions/ublock.js/js/text-encode.js | 275 + .../extensions/ublock.js/js/text-utils.js | 107 + .../extensions/ublock.js/js/traffic.js | 1164 ++ .../extensions/ublock.js/js/ublock.js | 658 + .../extensions/ublock.js/js/udom.js | 778 + .../extensions/ublock.js/js/uri-utils.js | 175 + .../ublock.js/js/url-net-filtering.js | 336 + .../extensions/ublock.js/js/utils.js | 197 + .../ublock.js/js/vapi-background-ext.js | 241 + .../ublock.js/js/vapi-background.js | 1611 ++ .../ublock.js/js/vapi-client-extra.js | 312 + .../extensions/ublock.js/js/vapi-client.js | 280 + .../extensions/ublock.js/js/vapi-common.js | 196 + .../extensions/ublock.js/js/vapi.js | 89 + .../extensions/ublock.js/js/wasm/README.md | 24 + .../ublock.js/js/wasm/biditrie.wasm | Bin 0 -> 990 bytes .../extensions/ublock.js/js/wasm/biditrie.wat | 728 + .../extensions/ublock.js/js/wasm/hntrie.wasm | Bin 0 -> 1034 bytes .../extensions/ublock.js/js/wasm/hntrie.wat | 724 + .../extensions/ublock.js/js/webext.js | 164 + .../extensions/ublock.js/js/whitelist.js | 266 + .../ublock.js/lib/codemirror/LICENSE | 21 + .../ublock.js/lib/codemirror/README.md | 47 + .../lib/codemirror/addon/comment/comment.js | 211 + .../lib/codemirror/addon/display/panel.js | 133 + .../codemirror/addon/edit/closebrackets.js | 201 + .../codemirror/addon/edit/matchbrackets.js | 160 + .../lib/codemirror/addon/fold/foldcode.js | 157 + .../lib/codemirror/addon/fold/foldgutter.css | 20 + .../lib/codemirror/addon/fold/foldgutter.js | 163 + .../lib/codemirror/addon/hint/show-hint.css | 36 + .../lib/codemirror/addon/hint/show-hint.js | 509 + .../lib/codemirror/addon/merge/merge.css | 119 + .../lib/codemirror/addon/merge/merge.js | 1006 ++ .../addon/scroll/annotatescrollbar.js | 128 + .../addon/search/matchesonscrollbar.css | 8 + .../codemirror/addon/search/searchcursor.js | 296 + .../codemirror/addon/selection/active-line.js | 72 + .../lib/codemirror/lib/codemirror.css | 350 + .../lib/codemirror.js.REMOVED.git-id | 1 + .../extensions/ublock.js/lib/diff/README.md | 34 + .../ublock.js/lib/diff/swatinem_diff.js | 272 + .../extensions/ublock.js/lib/hsluv/LICENSE | 20 + .../extensions/ublock.js/lib/hsluv/README | 3 + .../ublock.js/lib/hsluv/hsluv-0.1.0.min.js | 8 + .../extensions/ublock.js/lib/lz4/README.md | 52 + .../ublock.js/lib/lz4/lz4-block-codec-any.js | 151 + .../ublock.js/lib/lz4/lz4-block-codec-js.js | 297 + .../ublock.js/lib/lz4/lz4-block-codec-wasm.js | 195 + .../ublock.js/lib/lz4/lz4-block-codec.wasm | Bin 0 -> 1226 bytes .../ublock.js/lib/lz4/lz4-block-codec.wat | 745 + .../lib/publicsuffixlist/publicsuffixlist.js | 641 + .../lib/publicsuffixlist/wasm/README.md | 29 + .../wasm/publicsuffixlist.wasm | Bin 0 -> 408 bytes .../wasm/publicsuffixlist.wat | 322 + .../extensions/ublock.js/lib/punycode.js | 493 + .../ublock.js/lib/regexanalyzer/README.md | 14 + .../ublock.js/lib/regexanalyzer/regex.js | 2142 +++ .../extensions/ublock.js/logger-ui.html | 221 + .../extensions/ublock.js/managed_storage.json | 73 + .../extensions/ublock.js/manifest.json | 104 + .../extensions/ublock.js/no-dashboard.html | 27 + .../extensions/ublock.js/popup-fenix.html | 109 + .../extensions/ublock.js/settings.html | 98 + .../extensions/ublock.js/shortcuts.html | 40 + .../extensions/ublock.js/support.html | 120 + .../web_accessible_resources/1x1.gif | Bin 0 -> 43 bytes .../web_accessible_resources/2x2.png | Bin 0 -> 68 bytes .../web_accessible_resources/32x32.png | Bin 0 -> 83 bytes .../web_accessible_resources/3x2.png | Bin 0 -> 68 bytes .../web_accessible_resources/README.txt | 11 + .../addthis_widget.js | 38 + .../web_accessible_resources/amazon_ads.js | 70 + .../web_accessible_resources/amazon_apstag.js | 43 + .../web_accessible_resources/ampproject_v0.js | 34 + .../web_accessible_resources/chartbeat.js | 30 + .../web_accessible_resources/click2load.html | 28 + .../doubleclick_instream_ad_status.js | 1 + .../ublock.js/web_accessible_resources/empty | 0 .../web_accessible_resources/epicker-ui.html | 75 + .../web_accessible_resources/fingerprint2.js | 37 + .../web_accessible_resources/fingerprint3.js | 45 + .../google-analytics_analytics.js | 110 + .../google-analytics_cx_api.js | 36 + .../google-analytics_ga.js | 130 + .../google-analytics_inpage_linkid.js | 28 + .../googlesyndication_adsbygoogle.js | 52 + .../googletagmanager_gtm.js | 43 + .../googletagservices_gpt.js | 150 + .../web_accessible_resources/hd-main.js | 46 + .../ligatus_angular-tag.js | 29 + .../web_accessible_resources/monkeybroker.js | 43 + .../mxpnl_mixpanel.js | 51 + .../web_accessible_resources/nobab.js | 87 + .../web_accessible_resources/nobab2.js | 42 + .../web_accessible_resources/noeval-silent.js | 28 + .../web_accessible_resources/noeval.js | 30 + .../web_accessible_resources/nofab.js | 63 + .../web_accessible_resources/noop-0.1s.mp3 | Bin 0 -> 813 bytes .../web_accessible_resources/noop-1s.mp4 | Bin 0 -> 3753 bytes .../web_accessible_resources/noop-vmap1.0.xml | 1 + .../web_accessible_resources/noop.html | 5 + .../web_accessible_resources/noop.js | 3 + .../web_accessible_resources/noop.txt | 1 + .../outbrain-widget.js | 47 + .../web_accessible_resources/popads-dummy.js | 30 + .../web_accessible_resources/popads.js | 40 + .../web_accessible_resources/prebid-ads.js | 26 + .../scorecardresearch_beacon.js | 31 + .../window.open-defuser.js | 115 + .../extensions/ublock.js/whitelist.html | 62 + src/java/net/i2p/i2pfirefox/I2PChromium.java | 6 +- 393 files changed, 213318 insertions(+), 2 deletions(-) create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/cs/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/de/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/en/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/en_GB/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/en_US/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/es/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/fr/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/hu/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/it/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/ja/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/ko/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/lv/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/nl/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/pl/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/ro/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/ru/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/sv/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/zh_CN/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/zh_TW/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/_metadata/verified_contents.json create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/bootstrap-theme.min.css create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/bootstrap.min.css create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/glyph.css create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/options.css create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/popup.css create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/recents.css create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/fonts/glyphicons-halflings-regular.eot create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/fonts/glyphicons-halflings-regular.svg create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/fonts/glyphicons-halflings-regular.ttf create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/fonts/glyphicons-halflings-regular.woff create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/fonts/glyphicons-halflings-regular.woff2 create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/gpl.txt create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/background.html create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/options.html create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/popup.html create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/recents.html create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/updated.html create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/IconAllowed.png create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/IconDisabled.png create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/IconForbidden.png create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/IconTemp.png create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/heartbig.png create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/heartsmall.png create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/icon128.png create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/icon16.png create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/icon24.png create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/icon32.png create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/icon48.png create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/bootstrap.min.js create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/common.js create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/jquery.js create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/options.js create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/pako.js create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/popup.js create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/recents.js create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/scriptsafe.js create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/ss.js create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/updated.js create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/webrtctest.js create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/yoyo.js.REMOVED.git-id create mode 100644 src/i2p.chromium.base.profile/extensions/scriptsafe.js/manifest.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/1p-filters.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/3p-filters.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/LICENSE.txt create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/ar/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/az/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/bg/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/bn/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/bs/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/ca/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/cs/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/cv/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/da/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/de/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/el/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/en/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/en_GB/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/eo/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/es/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/et/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/eu/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/fa/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/fi/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/fil/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/fr/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/fy/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/gl/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/he/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/hi/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/hr/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/hu/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/hy/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/id/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/it/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/ja/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/ka/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/kk/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/kn/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/ko/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/lt/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/lv/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/ml/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/mr/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/ms/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/nb/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/nl/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/no/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/oc/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/pl/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/pt_BR/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/pt_PT/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/ro/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/ru/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/sk/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/sl/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/so/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/sq/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/sr/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/sv/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/ta/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/te/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/th/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/tr/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/uk/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/ur/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/vi/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/zh_CN/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_locales/zh_TW/messages.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/_metadata/verified_contents.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/about.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/advanced-settings.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/asset-viewer.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/assets.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/resources/scriptlets.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/thirdparties/easylist-downloads.adblockplus.org/easylist.txt.REMOVED.git-id create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/thirdparties/easylist-downloads.adblockplus.org/easyprivacy.txt.REMOVED.git-id create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/thirdparties/pgl.yoyo.org/as/README.md create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/thirdparties/pgl.yoyo.org/as/serverlist create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/thirdparties/publicsuffix.org/list/effective_tld_names.dat create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/thirdparties/urlhaus-filter/LICENSE.md create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/thirdparties/urlhaus-filter/urlhaus-filter-online.txt create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/ublock/badlists.txt create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/ublock/badware.txt create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/ublock/filters-2020.txt create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/ublock/filters-2021.txt.REMOVED.git-id create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/ublock/filters-2022.txt create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/ublock/filters.txt.REMOVED.git-id create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/ublock/lan-block.txt create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/ublock/legacy.txt create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/ublock/privacy.txt create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/ublock/quick-fixes.txt create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/ublock/resource-abuse.txt create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/assets/ublock/unbreak.txt create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/background.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/cloud-ui.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/1p-filters.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/3p-filters.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/about.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/advanced-settings.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/asset-viewer.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/click2load.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/cloud-ui.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/codemirror.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/common.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/dashboard-common.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/dashboard.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/devtools.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/document-blocked.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/dyna-rules.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/epicker-ui.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/fa-icons.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/fonts/Inter/Inter-Regular.woff2 create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/fonts/Inter/Inter-SemiBold.woff2 create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/fonts/Inter/LICENSE.txt create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/fonts/Metropolis/Metropolis-Regular.woff2 create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/fonts/Metropolis/Metropolis-SemiBold.woff2 create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/fonts/Metropolis/README.md create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/fonts/Metropolis/UNLICENSE create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/logger-ui-inspector.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/logger-ui.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/popup-fenix.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/settings.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/shortcuts.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/support.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/themes/default.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/css/whitelist.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/dashboard.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/devtools.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/document-blocked.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/dyna-rules.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/img/cloud.png create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/img/fontawesome/LICENSE.txt create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/img/fontawesome/fontawesome-defs.svg create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/img/help16.png create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/img/icon_128.png create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/img/icon_16-off.png create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/img/icon_16.png create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/img/icon_32-off.png create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/img/icon_32.png create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/img/icon_64.png create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/img/material-design.svg create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/img/photon.svg create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/img/ublock-defs.svg create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/img/ublock.svg create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/is-webrtc-supported.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/1p-filters.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/3p-filters.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/about.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/advanced-settings.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/asset-viewer.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/assets.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/background.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/base64-custom.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/benchmarks.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/biditrie.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/cachestorage.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/click2load.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/cloud-ui.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/codemirror/search-thread.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/codemirror/search.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/codemirror/ubo-dynamic-filtering.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/codemirror/ubo-static-filtering.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/commands.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/console.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/contentscript-extra.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/contentscript.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/contextmenu.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/cosmetic-filtering.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/dashboard-common.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/dashboard.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/devtools.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/document-blocked.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/dyna-rules.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/dynamic-net-filtering.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/epicker-ui.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/fa-icons.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/filtering-context.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/filtering-engines.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/hnswitches.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/hntrie.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/html-filtering.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/httpheader-filtering.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/i18n.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/is-webrtc-supported.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/logger-ui-inspector.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/logger-ui.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/logger.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/lz4.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/messaging.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/pagestore.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/popup-fenix.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/redirect-engine.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/reverselookup-worker.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/reverselookup.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlet-filtering.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/cosmetic-logger.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/cosmetic-off.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/cosmetic-on.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/dom-inspector.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/dom-survey-elements.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/dom-survey-scripts.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/epicker.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/load-3p-css.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/load-large-media-all.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/load-large-media-interactive.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/noscript-spoof.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/should-inject-contentscript.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/subscriber.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/settings.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/shortcuts.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/start.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/static-ext-filtering-db.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/static-ext-filtering.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/static-filtering-io.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/static-filtering-parser.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/static-net-filtering.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/storage.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/support.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/tab.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/tasks.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/text-encode.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/text-utils.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/traffic.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/ublock.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/udom.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/uri-utils.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/url-net-filtering.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/utils.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/vapi-background-ext.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/vapi-background.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/vapi-client-extra.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/vapi-client.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/vapi-common.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/vapi.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/wasm/README.md create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/wasm/biditrie.wasm create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/wasm/biditrie.wat create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/wasm/hntrie.wasm create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/wasm/hntrie.wat create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/webext.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/js/whitelist.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/LICENSE create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/README.md create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/addon/comment/comment.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/addon/display/panel.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/addon/edit/closebrackets.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/addon/edit/matchbrackets.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/addon/fold/foldcode.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/addon/fold/foldgutter.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/addon/fold/foldgutter.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/addon/hint/show-hint.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/addon/hint/show-hint.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/addon/merge/merge.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/addon/merge/merge.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/addon/scroll/annotatescrollbar.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/addon/search/matchesonscrollbar.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/addon/search/searchcursor.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/addon/selection/active-line.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/lib/codemirror.css create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/codemirror/lib/codemirror.js.REMOVED.git-id create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/diff/README.md create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/diff/swatinem_diff.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/hsluv/LICENSE create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/hsluv/README create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/hsluv/hsluv-0.1.0.min.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/lz4/README.md create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/lz4/lz4-block-codec-any.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/lz4/lz4-block-codec-js.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/lz4/lz4-block-codec-wasm.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/lz4/lz4-block-codec.wasm create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/lz4/lz4-block-codec.wat create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/publicsuffixlist/publicsuffixlist.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/publicsuffixlist/wasm/README.md create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/publicsuffixlist/wasm/publicsuffixlist.wasm create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/publicsuffixlist/wasm/publicsuffixlist.wat create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/punycode.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/regexanalyzer/README.md create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/lib/regexanalyzer/regex.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/logger-ui.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/managed_storage.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/manifest.json create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/no-dashboard.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/popup-fenix.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/settings.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/shortcuts.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/support.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/1x1.gif create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/2x2.png create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/32x32.png create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/3x2.png create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/README.txt create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/addthis_widget.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/amazon_ads.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/amazon_apstag.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/ampproject_v0.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/chartbeat.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/click2load.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/doubleclick_instream_ad_status.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/empty create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/epicker-ui.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/fingerprint2.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/fingerprint3.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/google-analytics_analytics.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/google-analytics_cx_api.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/google-analytics_ga.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/google-analytics_inpage_linkid.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/googlesyndication_adsbygoogle.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/googletagmanager_gtm.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/googletagservices_gpt.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/hd-main.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/ligatus_angular-tag.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/monkeybroker.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/mxpnl_mixpanel.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/nobab.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/nobab2.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noeval-silent.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noeval.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/nofab.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop-0.1s.mp3 create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop-1s.mp4 create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop-vmap1.0.xml create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop.html create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop.txt create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/outbrain-widget.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/popads-dummy.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/popads.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/prebid-ads.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/scorecardresearch_beacon.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/window.open-defuser.js create mode 100644 src/i2p.chromium.base.profile/extensions/ublock.js/whitelist.html diff --git a/build.xml b/build.xml index c28ff69..40aea6a 100755 --- a/build.xml +++ b/build.xml @@ -8,7 +8,7 @@ - + @@ -36,6 +36,20 @@ + + + + + + + + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/cs/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/cs/messages.json new file mode 100644 index 0000000..de6a18a --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/cs/messages.json @@ -0,0 +1,858 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "Získejte zpět kontrolu nad webem a surfujte bezpečněji." + }, + "alldomains": { + "description": "On All Domains", + "message": "Na všech doménách" + }, + "allow": { + "description": "Allow", + "message": "Povolit" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "Povolené položky" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "Blokované položky" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "Povolit všechny blokované pro relaci" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "Blokovat nežádoucí obsah:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "odstranení nežádoucího obsahu ze známých reklamních malware domén /; domény získané z MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "Režim zacházení s nežádoucím obsahem:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "Uvolněný = domény na bílé listině nebudou blokovány; Přísný = domény na seznamu nežádoucích budou blokovány i kdyby byly na bílé listině" + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "Antisociální" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "Antisociální režim:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "vždy odstraní widgety/tlačítka, i pokud jsou na bílé listině" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "Pro kompletnější blokování navštivte Privacy Badger, Disconnect, Blur a / nebo uBlock origin se seznamy z Fanboy site" + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "Blokovat identifikaci prohlížeče podle audia:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "předchází identifikaci prohlížeče přes AudioContext API" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "Blokovat identifikaci prohlížeče podle baterie zařízení:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "předchází identifikaci prohlížeče přes Battery API" + }, + "behavior": { + "description": "Behaviour Settings", + "message": "Nastavení chování" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "Blokovat (doporučeno)" + }, + "block": { + "description": "Block", + "message": "Blokovat" + }, + "blocked": { + "description": "Blocked", + "message": "Blokováno" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "Zablokovat všechny povolené pro relaci" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "Blokovat enumeraci Bluetooth:" + }, + "bluetoothdesc": { + "description": "prevent having devices detected via the Bluetooth API", + "message": "předchází detekci zařízení skrz Bluetooth API" + }, + "bulkimport": { + "description": "bulk import", + "message": "hromadný import" + }, + "bulkbtn": { + "description": "Import to List", + "message": "Importovat do seznamu" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "Hromadný import" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "Vložte domény do textového pole níže. Každá doména by měla být na samostatném rádku." + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Ochrana proti identifikaci prohlížeče podle canvasu:" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page.

Please try visiting a website.", + "message": "ScriptSafe nemůže zpracovat tuto stránku.

Zkuste návštívit stránku na webu." + }, + "canvasblank": { + "description": "Blank Readout", + "message": "Vrátit prázdný" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "Vrátit náhodný" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "Zcela blokovat výčet" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "chrání před identifikací prohlížeče pomocí prvků <canvas>" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "Blokovat přístup k fontům skrz canvas:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "předchází zjištění systémových fontů skrz prvky <canvas>. Muže způsobovat problémy s Google Dokumenty." + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "Klasický styl nastavení:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "pokud je zaškrtnuto, zavře kartu možností po každém kliknutí na položku" + }, + "clear": { + "description": "Clear", + "message": "Vyčistit" + }, + "clearlow": { + "description": "clear", + "message": "vyčistit" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "Blokovat obdélníky na straně klienta:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "předchází identifikaci prohlížeče pomocí výpočtu obdélníků prvků. Muže způsobovat problémy s některými rozbalovacími nabídkami." + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "Předcházet zásahům do funkcí schránky:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "zabrání stránkám zasahovat do činností se schránkou" + }, + "close": { + "description": "Close", + "message": "Zavřít" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "Blokovat nežádoucí soubory cookie:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad / malware domains; below mode applies to this as well", + "message": "blokuje cookies ze známých reklamních a malware domén; režim nakládání s nežádoucím obsahem se na toto vztahuje také" + }, + "custom": { + "description": "Custom", + "message": "Vlastní" + }, + "default": { + "description": "Default", + "message": "Výchozí" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "Chránit lokální IP" + }, + "deny": { + "description": "Deny", + "message": "Zamítnout" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "Chránit místní a veřejné IP adresy" + }, + "disable": { + "description": "Disable", + "message": "Zakázat" + }, + "disabled": { + "description": "disabled", + "message": "zakázáno" + }, + "disabledcap": { + "description": "Disabled", + "message": "Zakázáno" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Synchronizace je vypnutá.\r\nNeváhejte a přejít na stránku Možnosti pro nastavení synchronizace (případně vytvořte zálohu nastavení)." + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "Zakázat a odebrat:" + }, + "distrust": { + "description": "Distrust", + "message": "Nedůvěřovat" + }, + "distrustlow": { + "description": "distrust", + "message": "nedůvěřovat" + }, + "domain": { + "description": "Same Domain", + "message": "Stejná doména" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "Třídit dle domény:" + }, + "domaininfo": { + "description": "Help", + "message": "Pomoc" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "Neplatná doména/adresa" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "Doména nebo adresa musí obsahovat nějaká písmena/čísla" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see Block Unwanted Content and/or Antisocial Mode)", + "message": "Doménu nelze přidat, neboť je poskytovatelem nežádoucího obsahu (viz blokování nežádoucího obsahu a/nebo antisociální režim)" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "třídí seznamy adres URL na této stránce a na panelu podle domén" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "Tip: stiskněte CTRL+F pro hledání v seznamech" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "Tuto stránku znovu nezobrazovat" + }, + "enable": { + "description": "Enable:", + "message": "Povolit:" + }, + "enabled": { + "description": "enabled", + "message": "povoleno" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "Povolit ScriptSafe" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "Povolit synchronizaci:" + }, + "export": { + "description": "Export", + "message": "Export" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "Nastavení byla úspešně synchronizována!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "Nastavení synchronizována!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "Ochrana před identifikací prohlížeče" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "Ochrana před identifikací prohlížeče pomocí fingerprintingu (může rozbít stránky)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "Zdá se, že jste doposud nesesynchronizovali svá nastavení s účtem Google.\r\nScriptSafe se chystá provést synchronizaci aktuálního nastavení do Vašeho účtu Google.\r\nKlikněte na 'OK', chcete-li pokračovat.\r\nPokud ne, klikněte na tlačítko 'Storno', na druhém zařízení s požadovaným nastavením aktualizujte ScriptSafe a po zobrazení této zprávy klikněte na OK." + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "Chcete synchronizovat aktuální nastavení do svého účtu Google?\r\nPoznámka: neklikejte příliš často; limit je 10 za minutu a 1000 za hodinu." + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "Chcete importovat synchronizovaná nastavení ze svého účtu Google do tohoto zařízení?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "Klávesové zkratky:" + }, + "generalsettings": { + "description": "General Settings", + "message": "Obecná nastavení" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "Blokovat enumeraci gamepadu:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "předchází detekci zařízení skrz Gamepad API" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "Dostupné akce klávesových zkratek" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "Dočasně povolit/zablokovat všechny prostředky na aktuální kartě" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "Zrušit dočasná oprávnění aktuální karty" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "Zrušit veškerá dočasná oprávnění" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "Konfigurace klávesových zkratek ScriptSafe" + }, + "hotkeysinst": { + "description": "click on Keyboard Shortcuts", + "message": "klikněte na Klávesové zkratky" + }, + "listallsettings": { + "description": "List All Settings", + "message": "Zobrazit všechna nastavení" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "Povolit ignorované" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "Aktuální nastavení byla úspěšně stažena!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "Nastavení stažena!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "Seskupit všechna nastavení" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "Pokusit se odstranit sledovací tokeny:" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "odstraní prípadné sledovací tokeny předané pomocí hashe jako atribut a hodnota (např. #xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "Skrýt" + }, + "import": { + "description": "Import", + "message": "Import" + }, + "importexport": { + "description": "Import / Restore Settings", + "message": "Import / Obnova nastavení" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "Nastavení úspěšně importována" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "Nastavení úspěšně importována, s výjimkou následujících (prázdné hodnoty nebo nerozpoznané jméno):" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "Nastavení úspešně importována, synchronizace proběhne za 10 sekund" + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "Omezit možnost identifikace prohlížeče pomocí klávesnice (pro pokročilé uživatele):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "rozhází čas mezi stisky kláves pro zvýšení anonymity (Poznámka: přidává náhodnou prodlevu mezi stisky kláves, pokud je toto nepřijatelné, tuto možnost zakažte)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behaviour:", + "message": "Chování při otevírání odkazu:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "mění způsob nakládání se všemi odkazy" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "Volné - umožnit stejnou doménu a subdomény" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "Výchozí režim" + }, + "newtab": { + "description": "New Tab", + "message": "Nová karta" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "Tato karta nenačetla žádné externí zdroje" + }, + "notfiltered": { + "description": "Not filtered", + "message": "Nefiltrováno" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "Tento prohlížeč nepodporuje ochranu WebRTC" + }, + "off": { + "description": "-Off-", + "message": "-Vypnuto-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "Pouze na doménách mimo bílou listinu" + }, + "options": { + "description": "Options", + "message": "Možnosti" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "Režim paranoia:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "blokuje povolené domény na doménách mimo seznam" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "Vložte nastavení a zkuste to znovu" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "Respektovat stejnou doménu:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "zachová prvky ze stejné domény" + }, + "privacy": { + "description": "Privacy Settings", + "message": "Nastavení ochrany soukromí" + }, + "random": { + "description": "Random", + "message": "Náhodný" + }, + "rating": { + "description": "Show Rating Button:", + "message": "Zobrazit tlačítko hodnocení:" + }, + "ratingbtn": { + "description": "Rating", + "message": "Hodnocení" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "pokud je zaškrtnuto, zobrazí tlačítko hodnocení pod doménami v menu karty" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafe byl nedávno aktualizován/načten.

Aby ScriptSafe fungoval, musíte buď tuto kartu znovu načíst, otevřít novou, nebo restartovat prohlížeč." + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "Blokovat click-through referer:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to On All Domains may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "blokuje informace o refereru při kliknutí na odkazy třetích stran (poznámka: volba 'Na všech doménách' může způsobovat problémy (například miniatury v TweetDeck))" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "Podvrhnout referer:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may break some sites (e.g. logging in)", + "message": "pozor:: pokud je povoleno, může rozbít některé funkce stránek stránky (např. Přihlášení)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "Automaticky aktualizovat stránku:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "automaticky aktualizuje stránku po každé změně seznamu" + }, + "relaxed": { + "description": "Relaxed", + "message": "Uvolněný" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "Odvolat dočasná povolení stránky" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "Odvolat vše dočasné" + }, + "same": { + "description": "Same Document", + "message": "Stejný dokument" + }, + "sametab": { + "description": "Same Tab", + "message": "Stejná karta" + }, + "save": { + "description": "Save", + "message": "Uložit" + }, + "savetxt": { + "description": "Save as Text File", + "message": "Uložit jako textový soubor" + }, + "sections": { + "description": "Sections", + "message": "Oddíly" + }, + "settingsall": { + "description": "select all", + "message": "vybrat vše" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "Zkopírujte a vložte nastavení, které chcete importovat do ScriptSafe do tohoto pole a pak klikněte na tlačítko Import." + }, + "settingssave": { + "description": "Settings saved", + "message": "Nastavení byla uložena" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "Nastavení uložena, synchronizace proběhne za 10 sekund" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "Zobrazit v Kontextové Nabídce:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafe je zakázán" + }, + "strict": { + "description": "Strict", + "message": "Přísný" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "Přísný - povolit pouze stejnou doménu" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "Pro podporu vývoje klikněte na srdce :)" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on Sync Settings FROM Google Account.", + "message": "Povolili jste automatickou synchronizaci. Abyste zabránili smazání dosud synchronizovaných dat (pokud nějaká jsou), prosím klikněte na 'Synchronizovat data Z účtu Google'." + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "Synchronizovat data Z účtu Google" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "Synchronizovat data DO účtu Google" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "ScriptSafe zjistil, že máte na svém účtu Google synchronizovaná nastavení.\r\nKlikněte na 'OK', pokud chcete importovat nastavení ze svého účtu Google." + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Synchronizace byla zakázána, aby nedošlo k přepsání již synchronizovaných dat.\r\nNeváhejte přejít na stránku Možnosti pro nastavení synchronizace (případně vytvořte zálohu nastavení)." + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "Zobrazit oznámení při synchronizaci z účtu:" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": "zobrazí vyskakovací okno po synchronizaci z účtu Google" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "Zobrazit oznámení o synchronizaci:" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "zobrazí vyskakovací okno po synchronizaci do účtu Google" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "Vaše verze prohlížeče neumožňuje synchronizaci nastavení. Prosím zkuste aktualizovat Chrome a zkuste to znovu." + }, + "temp": { + "description": "Temporary", + "message": "Dočasný" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "Podvrhnout časové pásmo:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "podvrhne nebo randomizuje časové pásmo. Poznámka: může způsobovat problémy s odpovídáním na e-maily ve službě Gmail." + }, + "trust": { + "description": "Trust", + "message": "Důvěřovat" + }, + "trustlow": { + "description": "trust", + "message": "důvěřovat" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "Aplikovat i na domény na bílé listině" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside Show Update Popup.", + "message": "Jste si jisti, že chcete zakázat jakákoli budoucí oznámení o aktualizaci, jako je tento?\r\nZnovu povolit je můžete na stránce Možnosti, zaškrtnutím políčka 'Zobrazovat oznámení o aktualizaci'." + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "Oznámení o aktualizaci zakázána" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "Při aktualizaci zobrazovat seznam změn:" + }, + "unwanted": { + "description": "Unwanted", + "message": "Nechtěný" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "zobrazí stránku seznamu změn po aktualizaci ScriptSafe" + }, + "url": { + "description": "Domain", + "message": "Doména" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "Zadejte doménu nebo výraz (klikněte na 'Pomoc' pro více informací)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "Podvrhnout User-Agent:" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "nastaví smyšlený user-agent (prohlížeče a OS)" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "Zadejte adresu odesílanou jako referer pro všechny weby" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "Odstranit sledování Google Analytics (UTM):" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "odebere Google Analytics (UTM) sledovací tokeny" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "Odstranit Webbugs:" + }, + "webbugsdesc": { + "description": "remove invisible third-party elements", + "message": "odstraní neviditelné elementy třetích stran" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "Blokovat identifikaci prohlížeče pomocí WebGL:" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "zabrání identifikaci prohlížeče pomocí WebGL API" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "Blokovat detekci zařízení:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "zabrání detekci hardwaru pomocí WebRTC API" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "Ochrana WebRTC:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "zamezí úniku IP adres" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "Blokovat enumeraci WebVR:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "předchází detekci zařízení skrz WebVR API" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ Černá listina" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ Bílá listina" + }, + "blacklist": { + "description": "Blacklist", + "message": "Černá Listina" + }, + "whitelist": { + "description": "Whitelist", + "message": "Bílá listina" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "Na černé listině" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "Na bílé listině" + }, + "blacklistlow": { + "description": "blacklist", + "message": "černá listina" + }, + "whitelistlow": { + "description": "whitelist", + "message": "bílá listina" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "Přesunout na Černou listinu" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "Přesunout na Bílou listinu" + }, + "whitelistblacklist": { + "description": "Whitelist / Blacklist", + "message": "Bílá listina / Černá listina" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "Zacházení s XML HTTP požadavky:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "Kontrolovat všechny požadavky" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "Kontrolovat požadavky napříč doménami (povolit stejnou doménu)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "kontroluje XML HTTP požadavky" + } +} diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/de/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/de/messages.json new file mode 100644 index 0000000..a621c26 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/de/messages.json @@ -0,0 +1,858 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "Erlangen Sie Kontrolle über das Internet und surfen Sie sicherer." + }, + "alldomains": { + "description": "On All Domains", + "message": "Auf allen Domains" + }, + "allow": { + "description": "Allow", + "message": "Zulassen" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "Erlaubte Objekte" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "Blockierte Objekte" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "Erlaube alle blockierten Inhalte für diese Session" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "Blockiere unerwünschten Inhalt:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "Entfernen Sie unerwünschte Inhalte aus bekannten Ad / Malware-Domains; Domains gesammelt von MVPS HOSTS, hpHOSTS ( ad / Tracking-Server), Peter Lowe HOSTS-Projekt, MalwareDomainList.com" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "Unerwünschte Inhalte-Modus:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "Entspannt = gewhitelistete Domains werden nicht blockiert; Strikt = Domainn in der unerwünschte Domainnliste werden auch blockiert, selbst wenn sie per Whitelist zugelassen wurden." + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "Unsozial" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "Azozial-Modus:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "Entferne immer soziale Widgets / Buttons, sogar wenn sie per Whitelist zugelassen wurden" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "Für umfassenderes Blocken, probieren Sie Privacy Badger Disconnect, Blur, und / oder ublock Origin mit all den Abonnement-Listen auf der Fanboy Website" + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "Blockiere Audio-Fingerprinting:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "Verhindert Fingerprinting über die AudioContext API" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "blockiere Batterie-Fingerprinting:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "Verhindert Fingerprinting über die Batterie-API" + }, + "behavior": { + "description": "Behaviour Settings", + "message": "Verhaltenseinstellungen" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "Blockieren (empfohlen)" + }, + "block": { + "description": "Block", + "message": "Blockieren" + }, + "blocked": { + "description": "Blocked", + "message": "Blockiert" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "Alle Erlaubten für diese Session blockieren" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "Blockiere Bluetooth Enumeration:" + }, + "bluetoothdesc": { + "description": "prevent having devices detected via the Bluetooth API", + "message": "Verhindert, dass Geräte über das Bluetooth API erkannt werden" + }, + "bulkimport": { + "description": "bulk import", + "message": "Massenimport" + }, + "bulkbtn": { + "description": "Import to List", + "message": "Zur Liste importieren" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "Massenimport" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "Kopiere und füge Domainn in das folgende Feld ein. Jede Domäne sollte in einer separaten Zeile stehen." + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Canvas Fingerabdruck-Schutz:" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page.

Please try visiting a website.", + "message": "ScriptSafe konnte diese Seite nicht verarbeiten.

Bitte versuchen Sie eine Webseite zu besuchen." + }, + "canvasblank": { + "description": "Blank Readout", + "message": "Leere Ausgabe" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "Zufällige Ausgabe" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "Ausgabe komplett blockieren" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "Schutz vor Fingerprinting-Versuche durch <canvas> Elemente" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "Blockiere Canvas-Font-Zugang:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "Verhindert, dass Systemschriften durch <canvas> Elemente aufgezählt werden. Kann mit Google Docs Probleme bereiten." + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "Klassischer Optionen Modus:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "wenn aktiviert, schließt sich die Optionen-Registerkarte jedes Mal wenn eine Option angeklickt wird" + }, + "clear": { + "description": "Clear", + "message": "Leeren" + }, + "clearlow": { + "description": "clear", + "message": "leeren" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "Blockiere Client-Rechtecke:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "Verhindert Fingerprinting indem Rechteck-Elemente berechnet werden. Kann mit einigen Aufklappmenüs Probleme verursachen." + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "Verhindert Zwischenablage Interferenz:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "Verhindert, dass Webseiten sich in Zwischenablage-Aktionen einmischen können" + }, + "close": { + "description": "Close", + "message": "Schließen" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "Blockieren Sie unerwünschte Cookies:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad / malware domains; below mode applies to this as well", + "message": "Blockiert Cookies aus bekannten Anzeige / Malware-Domains; der untere Modus gilt auch für dies" + }, + "custom": { + "description": "Custom", + "message": "Eigene" + }, + "default": { + "description": "Default", + "message": "Standard" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "Schützen Sie Ihre lokale IP" + }, + "deny": { + "description": "Deny", + "message": "Verweigern" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "Schützt lokale und öffentliche IP-Adressen" + }, + "disable": { + "description": "Disable", + "message": "Deaktivieren" + }, + "disabled": { + "description": "disabled", + "message": "deaktiviert" + }, + "disabledcap": { + "description": "Disabled", + "message": "Deaktiviert" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Syncing ist deaktiviert.\r\nZögern Sie nicht auf die Optionen-Seite zu gehen, um Ihre Einstellungen zu synchronisieren (stellen Sie eine Sicherungskopie Ihrer Einstellungen falls erforderlich)." + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "deaktivieren und entfernen von:" + }, + "distrust": { + "description": "Distrust", + "message": "Misstrauen" + }, + "distrustlow": { + "description": "distrust", + "message": "misstrauen" + }, + "domain": { + "description": "Same Domain", + "message": "Gleiche Domain" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "Sortieren nach Domain:" + }, + "domaininfo": { + "description": "Help", + "message": "Hilfe" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "Ungültige Domain / Adresse" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "Die Domain oder Adresse muss einige Buchstaben / Ziffern enthalten" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see Block Unwanted Content and/or Antisocial Mode)", + "message": "Domain kann nicht hinzugefügt werden, da sie ein Anbieter von unerwünschten Inhalten ist (siehe blockiere unerwünschte Inhalte und / oder Unsozial-Modus)" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "sortiert URL-Listen nach Domain auf dieser Seite und im Feld" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "Tipp: Drücken Sie STRG + F, um die Listen zu durchsuchen" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "Diese Seite nicht mehr anzeigen" + }, + "enable": { + "description": "Enable:", + "message": "Aktivieren:" + }, + "enabled": { + "description": "enabled", + "message": "aktiviert" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "Aktivieren Sie ScriptSafe" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "Aktivieren Sie Syncing:" + }, + "export": { + "description": "Export", + "message": "Export" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "Ihre Einstellungen wurden erfolgreich synchronisiert!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "Einstellungen synchronisiert!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "Fingerabdruck-Schutz" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "Fingerabdruck-Schutz (könnte Websites kapput machen)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "Es scheint, dass Sie Ihre Einstellungen mit Ihrem Google-Konto noch nicht synchronisiert haben.\r\nScriptSafe wird Ihre aktuellen Einstellungen mit Ihrem Google-Konto synchronisieren.\r\nKlicken Sie auf \"OK\", wenn Sie den Vorgang fortsetzen möchten.\r\nWenn nicht , klicken Sie auf \"Abbrechen\", und auf dem anderen Gerät mit Ihren bevorzugten Einstellungen aktualisieren Sie ScriptSafe und klicken Sie auf OK, wenn diese Meldung dargestellt wird." + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "Möchten Sie Ihre aktuellen Einstellungen in Ihrem Google-Konto synchronisieren\r\nHinweis: Bitte drücken Sie dies nicht häufig; es gibt eine Grenze von 10 pro Minute und 1.000 pro Stunde." + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "Möchten Sie die synchronisierten Einstellungen von Ihrem Google-Konto auf diesem Gerät importieren?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "Hotkeys:" + }, + "generalsettings": { + "description": "General Settings", + "message": "Allgemeine Einstellungen" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "Blockiere Gamepad Enumeration:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "Verhindert, dass Geräte über das Gamepad API erkannt werden" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "Verfügbare Hotkey Aktionen" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "Vorübergehend alle Resourcen für einen Tab erlauben" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "Entfernt temporäre Berechtigungen für einen Tab" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "Entfernen Sie alle temporären Berechtigungen" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "ScriptSafe Hotkeys konfigurieren" + }, + "hotkeysinst": { + "description": "click on Keyboard Shortcuts", + "message": "Klicken Sie auf Tastaturbefehle" + }, + "listallsettings": { + "description": "List All Settings", + "message": "Alle Einstellungen auflisten" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "Ignoriere Zugelassene" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "Die neuesten Einstellungen wurden erfolgreich heruntergeladen!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "Einstellungen heruntergeladen!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "Gruppiere alle Einstellungen" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "Entfernen Sie mögliches Hash-Tracking:" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "entfernt mögliche Nachverfolgungstoken, welche mit Hash übergeben wurden, bei denen es ein Attribut und einen Wert gibt (z.B. #xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "Verstecken" + }, + "import": { + "description": "Import", + "message": "Import" + }, + "importexport": { + "description": "Import / Restore Settings", + "message": "Einstellungen importieren / wiederherstellen" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "Einstellungen erfolgreich importiert" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "Einstellungen erfolgreich importiert, mit Ausnahme der folgenden (leerer Wert oder nicht erkannter Name):" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "Einstellungen erfolgreich importiert. Synchronisation in 10 Sekunden" + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "Reduzieren Sie Tastatur-Fingerprinting (für fortgeschrittene Benutzer):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "macht Tastendruck-Timings zufälliger um die Anonymität zu erhöhen (Hinweis: fügt eine zufällige Verzögerung zwischen Tastendrücken, deaktivieren Sie diese Einstellung, wenn Sie sich inakzeptabel anfühlt)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behaviour:", + "message": "Seiten-Link Öffnungsverhalten:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "modifiziert, wie alle Verbindungen geöffnet werden" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "Lose - erlaubt gleiche Domain und Subdomains" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "Standardmodus" + }, + "newtab": { + "description": "New Tab", + "message": "Neuer Tab" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "Dieser Tab hat keine externen Ressourcen geladen" + }, + "notfiltered": { + "description": "Not filtered", + "message": "Nicht gefiltert" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "Dieser Browser unterstützt keinen WebRTC Schutz" + }, + "off": { + "description": "-Off-", + "message": "-Aus-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "Nur auf nicht per Whitelist freigegebenen Domains" + }, + "options": { + "description": "Options", + "message": "Optionen" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "Paranoia-Modus:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "Blockiere erlaubte Domains auf nicht gelisteten Domains" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "Fügen Sie dies in die Einstellungen ein und versuchen Sie es erneut" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "Respektiere Gleiche-Domain:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "erhält Elemente der gleichen Domain" + }, + "privacy": { + "description": "Privacy Settings", + "message": "Datenschutzeinstellungen" + }, + "random": { + "description": "Random", + "message": "Zufällig" + }, + "rating": { + "description": "Show Rating Button:", + "message": "Bewertungs Button zeigen:" + }, + "ratingbtn": { + "description": "Rating", + "message": "Wertung" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "Wenn aktiviert, fügt einen Rating-Button unter der Domain in Tab-Popup hinzu" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafe wurde vor kurzem aktualisiert / neu geladen.

Sie müssen entweder diesen Tab aktualisieren, einen neuen Tab öffen oder Ihren Browser neustarten, um ScriptSafe wieder zum Laufen zu bringen." + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "Blockieren Sie Click-Through-Werber:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to On All Domains may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "blockiert Referrer Informationen, wenn Sie auf fremden Links klicken (Anmerkung: Diese Einstellung auf alle Domains einzustellen könnte Probleme verursachen (z.B. Thumbnails in Tweetdeck))" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "Referrer-Spoof:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may break some sites (e.g. logging in)", + "message": "Warnung: Wenn aktiviert, könnten einige Seiten nicht mehr funktionieren (z.B. anmelden)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "Seite automatisch aktualisieren:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "Seite automatisch aktualisieren, nachdem Listen geändert wurden" + }, + "relaxed": { + "description": "Relaxed", + "message": "Entspannt" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "Temporäre Website Berechtigungen aufheben" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "Alle Temporären aufheben" + }, + "same": { + "description": "Same Document", + "message": "Selbes Dokument" + }, + "sametab": { + "description": "Same Tab", + "message": "Same Tab" + }, + "save": { + "description": "Save", + "message": "Speichern" + }, + "savetxt": { + "description": "Save as Text File", + "message": "Speichern als Textdatei" + }, + "sections": { + "description": "Sections", + "message": "Abschnitte" + }, + "settingsall": { + "description": "select all", + "message": "Alles auswählen" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "Kopieren Sie und fügen Sie die Einstellungen in dieses Feld ein, die Sie in ScriptSafe importieren wollen, dann klicken Sie auf die Importieren Schaltfläche ." + }, + "settingssave": { + "description": "Settings saved", + "message": "Einstellungen gespeichert" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "Einstellungen gespeichert, Synchronisierung startet in 10 Sekunden" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "Im Kontextmenü anzeigen:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafe ist deaktiviert" + }, + "strict": { + "description": "Strict", + "message": "Strikt" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "Strikt - nur die gleiche Domain erlauben" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "Um die Entwicklung zu unterstützen, klicken Sie auf das Herz :)" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on Sync Settings FROM Google Account.", + "message": "Sie haben die automatische Synchronisierung aktiviert. Um die Löschung Ihrer zuvor synchronisierten Daten (falls vorhanden) zu verhindern, klicken Sie bitte auf Einstellungen vom Google-Konto synchronisieren." + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "Einstellungen vom Google-Konto synchronisieren" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "Einstellungen auf das Google-Konto synchronisieren" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "ScriptSafe hat erkannt, dass Sie Einstellungen auf Ihr Google-Konto synchronisiert haben!\r\nKlicken Sie auf \"OK\", wenn Sie die Einstellungen von Ihrem Google-Konto importieren möchten." + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Syncing wurde deaktiviert, damit Ihre bereits synchronisierten Daten nicht überschrieben werden.\r\nZögern Sie nicht auf die Optionenseite zu gehen, um Ihre Einstellungen zu synchronisieren (erstellen Sie eine Sicherungskopie Ihrer Einstellungen falls erforderlich)." + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "Import Sync Mitteilung zeigen:" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": "Popup anzeigen, wenn die Einstellungen von Ihrem Google-Konto synchronisiert wurden" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "Sync Mitteilung zeigen:" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "Popup anzeigen, wenn die Einstellungen auf Ihr Google-Konto synchronisiert wurden" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "Ihre aktuelle Version von Google Chrome unterstützt nicht die Synchronisation Ihrer Einstellungen. Bitte versuchen Sie Ihre Chrome-Version zu aktualisieren und versuchen Sie es erneut." + }, + "temp": { + "description": "Temporary", + "message": "Vorübergehend" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "Zeitzone spoofen:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "fälscht oder randomisiert Ihre Zeitzone . HINWEIS: Wenn aktiviert, kann es Probleme mit der Beantwortung von E-Mails in Gmail verursachen." + }, + "trust": { + "description": "Trust", + "message": "Vertrauen" + }, + "trustlow": { + "description": "trust", + "message": "vertrauen" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "Auch auf per Whitelist freigegebene Domains anwenden" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside Show Update Popup.", + "message": "Sind Sie sich sicher, dass in Zukunft keine Benachrichtigungen über Updates wie diese erscheinen sollen?\r\nSie können immer wieder Update-Benachrichtigungen erlauben, indem Sie auf die ScriptSafe Optionen-Seite gehen und das Feld neben Update Popup anzeigen anklicken." + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "Update-Benachrichtigungen deaktiviert" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "Changelog bei Update zeigen:" + }, + "unwanted": { + "description": "Unwanted", + "message": "Unerwünscht" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "Changelog Seite zeigen, wenn ScriptSafe aktualisiert wurde" + }, + "url": { + "description": "Domain", + "message": "Domain" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "Geben Sie eine Domain oder ein Ausdruck ein (klicken Sie auf \"Hilfe\" für weitere Informationen)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "User-Agent Spoof:" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "fälscht Ihren User-Agent (Browser und OS)" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "Geben Sie eine Adresse als Referrer Wert für alle Webseiten ein ein" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "Entfernen Sie Google Analytics (UTM) Tracking:" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "entfernt Google Analytics (UTM) Nachverfolgungstoken" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "Webbugs entfernen :" + }, + "webbugsdesc": { + "description": "remove invisible third-party elements", + "message": "entfernt unsichtbare Elemente von Drittanbietern" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "Blockiere WebGL Fingerprinting:" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "verhindert Fingerprinting über das WebGL-API" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "Blockiere Device Enumeration:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "verhindert, dass Hardware-Geräte über die WebRTC-API erkannt" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "WebRTC Schutz:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "verhindert IP-Adresse leakage" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "Blockiere WebVR Enumeration:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "Verhindert, dass Geräte über das WebVR API erkannt werden" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ Blacklist" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ Whitelist" + }, + "blacklist": { + "description": "Blacklist", + "message": "Blacklist" + }, + "whitelist": { + "description": "Whitelist", + "message": "Whitelist" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "Auf die Blacklist gesetzt" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "Auf die Whitelist gesetzt" + }, + "blacklistlow": { + "description": "blacklist", + "message": "blacklist" + }, + "whitelistlow": { + "description": "whitelist", + "message": "whitelist" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "Zur Blacklist hinzufügen" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "Zur Whitelist hinzufügen" + }, + "whitelistblacklist": { + "description": "Whitelist / Blacklist", + "message": "White- / Blackliste" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "XML-HTTP-Request-Handling:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "Steuern Sie alle Anfragen" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "Cross-Domain Requests kontrollieren (gleiche Domain erlauben)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "XML HTTP-Requests steuern" + } +} diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/en/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/en/messages.json new file mode 100644 index 0000000..faa4b7a --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/en/messages.json @@ -0,0 +1,858 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "Regain control of the web and surf more securely." + }, + "alldomains": { + "description": "On All Domains", + "message": "On All Domains" + }, + "allow": { + "description": "Allow", + "message": "Allow" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "Allowed Items" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "Blocked Items" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "Allow All Blocked For Session" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "Block Unwanted Content:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "Unwanted Content Mode:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted" + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "Antisocial" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "Antisocial Mode:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "always remove social widgets/buttons, even if whitelisted" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site" + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "Block Audio Fingerprinting:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "prevent fingerprinting via the AudioContext API" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "Block Battery Fingerprinting:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "prevent fingerprinting via the Battery API" + }, + "behavior": { + "description": "Behavior Settings", + "message": "Behavior Settings" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "Block (recommended)" + }, + "block": { + "description": "Block", + "message": "Block" + }, + "blocked": { + "description": "Blocked", + "message": "Blocked" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "Block All Allowed For Session" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "Block Bluetooth Enumeration:" + }, + "bluetoothdesc": { + "description": "prevent having devices detected via the Bluetooth API", + "message": "prevent having devices detected via the Bluetooth API" + }, + "bulkimport": { + "description": "bulk import", + "message": "bulk import" + }, + "bulkbtn": { + "description": "Import to List", + "message": "Import to List" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "Bulk Import" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "Copy and paste domains into the box below. Each domain should be on a separate line." + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Canvas Fingerprint Protection:" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page.

Please try visiting a website.", + "message": "ScriptSafe cannot process this page.

Please try visiting a website." + }, + "canvasblank": { + "description": "Blank Readout", + "message": "Blank Readout" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "Random Readout" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "Completely Block Readout" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "protect against fingerprinting attempts through <canvas> elements" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "Block Canvas Font Access:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs." + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "Classic Options Mode:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "if ticked, closes tab options everytime an option is clicked" + }, + "clear": { + "description": "Clear", + "message": "Clear" + }, + "clearlow": { + "description": "clear", + "message": "clear" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "Block Client Rectangles:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns." + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "Prevent Clipboard Interference:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "prevent pages from interfering with clipboard actions" + }, + "close": { + "description": "Close", + "message": "Close" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "Block Unwanted Cookies:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad / malware domains; below mode applies to this as well", + "message": "blocks cookies from known ad / malware domains; below mode applies to this as well" + }, + "custom": { + "description": "Custom", + "message": "Custom" + }, + "default": { + "description": "Default", + "message": "Default" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "Protect Local IP" + }, + "deny": { + "description": "Deny", + "message": "Deny" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "Protect Local and Public IPs" + }, + "disable": { + "description": "Disable", + "message": "Disable" + }, + "disabled": { + "description": "disabled", + "message": "disabled" + }, + "disabledcap": { + "description": "Disabled", + "message": "Disabled" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary)." + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "Disable and Remove:" + }, + "distrust": { + "description": "Distrust", + "message": "Distrust" + }, + "distrustlow": { + "description": "distrust", + "message": "distrust" + }, + "domain": { + "description": "Same Domain", + "message": "Same Domain" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "Sort by Domain:" + }, + "domaininfo": { + "description": "Help", + "message": "Help" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "Invalid domain/address" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "The domain or address must contain some letters/numbers" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see \"Block Unwanted Content\" and/or \"Antisocial Mode\")", + "message": "Domain cannot be added as it is a provider of unwanted content (see \"Block Unwanted Content\" and/or \"Antisocial Mode\")" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "sorts URL lists by domains on this page and in the panel" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "Tip: press CTRL+F to search the lists" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "Don't Show This Page Again" + }, + "enable": { + "description": "Enable:", + "message": "Enable:" + }, + "enabled": { + "description": "enabled", + "message": "enabled" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "Enable ScriptSafe" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "Enable Syncing:" + }, + "export": { + "description": "Export", + "message": "Export" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "Your settings have been successfully synced!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "Settings Synced!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "Fingerprint Protection" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "Fingerprint Protection (may break sites)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message." + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour." + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "Do you want to import the synced settings from your Google Account to this device?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "Hotkeys:" + }, + "generalsettings": { + "description": "General Settings", + "message": "General Settings" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "Block Gamepad Enumeration:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "prevent having devices detected via the Gamepad API" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "Available hotkey actions" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "Temporarily allow/block all resources for a current tab" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "Remove temporary permissions for a current tab" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "Remove all temporary permissions" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "Configure ScriptSafe hotkeys" + }, + "hotkeysinst": { + "description": "click on \"Keyboard Shortcuts\"", + "message": "click on \"Keyboard Shortcuts\"" + }, + "listallsettings": { + "description": "List All Settings", + "message": "List All Settings" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "Ignored Allow" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "The latest settings have been successfully downloaded!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "Settings Downloaded!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "Group All Settings" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "Remove Possible Hash Tracking:" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "Hide" + }, + "import": { + "description": "Import", + "message": "Import" + }, + "importexport": { + "description": "Import / Restore Settings", + "message": "Import / Restore Settings" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "Settings imported successfully" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "Settings imported successfully, except the following (empty value or unrecognized name):" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "Settings imported successfully and syncing in 10 seconds" + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "Reduce Keyboard Fingerprinting (for advanced users):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behavior:", + "message": "Page Link Opening Behavior:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "modifies how all links are opened" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "Loose - allow same domain and subdomains" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "Default Mode" + }, + "newtab": { + "description": "New Tab", + "message": "New Tab" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "This tab has loaded no external resources" + }, + "notfiltered": { + "description": "Not filtered", + "message": "Not filtered" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "This browser does not support WebRTC protection" + }, + "off": { + "description": "-Off-", + "message": "-Off-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "Only on Unwhitelisted Domains" + }, + "options": { + "description": "Options", + "message": "Options" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "Paranoia Mode:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "block allowed domains on unlisted domains" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "Paste in settings and try again" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "Respect Same-Domain:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "preserve same-domain elements" + }, + "privacy": { + "description": "Privacy Settings", + "message": "Privacy Settings" + }, + "random": { + "description": "Random", + "message": "Random" + }, + "rating": { + "description": "Show Rating Button:", + "message": "Show Rating Button:" + }, + "ratingbtn": { + "description": "Rating", + "message": "Rating" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "if ticked, adds rating button under domains in tab popup" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work." + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "Block Click-Through Referrer:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to \"On All Domains\" may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "blocks referrer information when clicking on third-party links (note: setting this to \"On All Domains\" may cause issues (e.g. thumbnails in Tweetdeck))" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "Referrer Spoof:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may \"break\" some sites (e.g. logging in)", + "message": "warning: if enabled, may \"break\" some sites (e.g. logging in)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "Auto-Refresh Page:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "auto-refresh page after list change" + }, + "relaxed": { + "description": "Relaxed", + "message": "Relaxed" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "Revoke Page Temporary Permissions" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "Revoke All Temporary" + }, + "same": { + "description": "Same Document", + "message": "Same Document" + }, + "sametab": { + "description": "Same Tab", + "message": "Same Tab" + }, + "save": { + "description": "Save", + "message": "Save" + }, + "savetxt": { + "description": "Save as Text File", + "message": "Save as Text File" + }, + "sections": { + "description": "Sections", + "message": "Sections" + }, + "settingsall": { + "description": "select all", + "message": "select all" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button." + }, + "settingssave": { + "description": "Settings saved", + "message": "Settings saved" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "Settings saved and syncing in 10 seconds" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "Show in Context Menu:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafe is disabled" + }, + "strict": { + "description": "Strict", + "message": "Strict" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "Strict - allow same domain only" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "To support development, click the heart :)" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on \"Sync Settings FROM Google Account\".", + "message": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on \"Sync Settings FROM Google Account\"." + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "Sync Settings FROM Google Account" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "Sync Settings TO Google Account" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account." + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary)." + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "Show Import Sync Notification:" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": "show popup when settings synced from your Google Account" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "Show Sync Notification:" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "show popup when settings synced to your Google Account" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again." + }, + "temp": { + "description": "Temporary", + "message": "Temporary" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "Spoof Timezone:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail." + }, + "trust": { + "description": "Trust", + "message": "Trust" + }, + "trustlow": { + "description": "trust", + "message": "trust" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "Apply to whitelisted domains as well" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside \"Show Update Popup\".", + "message": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside \"Show Update Popup\"." + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "Update notifications disabled" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "Show Changelog on Update:" + }, + "unwanted": { + "description": "Unwanted", + "message": "Unwanted" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "show changelog page when ScriptSafe is updated" + }, + "url": { + "description": "Domain", + "message": "Domain" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "Enter a domain or expression (click 'Help' for more info)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "User-Agent Spoof:" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "spoofs your user-agent (browser and OS)" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "Enter an address to set as your referrer value for all sites" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "Remove Google Analytics (UTM) Tracking:" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "remove Google Analytics (UTM) tracking tokens" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "Remove Webbugs:" + }, + "webbugsdesc": { + "description": "remove \"invisible\" third-party elements", + "message": "remove \"invisible\" third-party elements" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "Block WebGL Fingerprinting:" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "prevent fingerprinting via the WebGL API" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "Block Device Enumeration:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "prevent having hardware devices detected via the WebRTC API" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "WebRTC Protection:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "prevent IP address leakage" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "Block WebVR Enumeration:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "prevent having devices detected via the WebVR API" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ Blacklist" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ Whitelist" + }, + "blacklist": { + "description": "Blacklist", + "message": "Blacklist" + }, + "whitelist": { + "description": "Whitelist", + "message": "Whitelist" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "Blacklisted" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "Whitelisted" + }, + "blacklistlow": { + "description": "blacklist", + "message": "blacklist" + }, + "whitelistlow": { + "description": "whitelist", + "message": "whitelist" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "Move to Blacklist" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "Move to Whitelist" + }, + "whitelistblacklist": { + "description": "Whitelist / Blacklist", + "message": "Whitelist / Blacklist" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "XML HTTP Request Handling:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "Control All Requests" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "Control Cross-Domain Requests (allow Same-Domain)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "control XML HTTP Requests" + } +} diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/en_GB/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/en_GB/messages.json new file mode 100644 index 0000000..0867925 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/en_GB/messages.json @@ -0,0 +1,858 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "Regain control of the web and surf more securely." + }, + "alldomains": { + "description": "On All Domains", + "message": "On All Domains" + }, + "allow": { + "description": "Allow", + "message": "Allow" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "Allowed Items" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "Blocked Items" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "Allow All Blocked For Session" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "Block Unwanted Content:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "Unwanted Content Mode:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted" + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "Antisocial" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "Antisocial Mode:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "always remove social widgets/buttons, even if whitelisted" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site" + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "Block Audio Fingerprinting:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "prevent fingerprinting via the AudioContext API" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "Block Battery Fingerprinting:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "prevent fingerprinting via the Battery API" + }, + "behavior": { + "description": "Behaviour Settings", + "message": "Behaviour Settings" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "Block (recommended)" + }, + "block": { + "description": "Block", + "message": "Block" + }, + "blocked": { + "description": "Blocked", + "message": "Blocked" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "Block All Allowed For Session" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "Block Bluetooth Enumeration:" + }, + "bluetoothdesc": { + "description": "prevent having devices detected via the Bluetooth API", + "message": "prevent having devices detected via the Bluetooth API" + }, + "bulkimport": { + "description": "bulk import", + "message": "bulk import" + }, + "bulkbtn": { + "description": "Import to List", + "message": "Import to List" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "Bulk Import" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "Copy and paste domains into the box below. Each domain should be on a separate line." + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Canvas Fingerprint Protection:" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page.

Please try visiting a website.", + "message": "ScriptSafe cannot process this page.

Please try visiting a website." + }, + "canvasblank": { + "description": "Blank Readout", + "message": "Blank Readout" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "Random Readout" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "Completely Block Readout" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "protect against fingerprinting attempts through <canvas> elements" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "Block Canvas Font Access:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs." + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "Classic Options Mode:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "if ticked, closes tab options everytime an option is clicked" + }, + "clear": { + "description": "Clear", + "message": "Clear" + }, + "clearlow": { + "description": "clear", + "message": "clear" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "Block Client Rectangles:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns." + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "Prevent Clipboard Interference:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "prevent pages from interfering with clipboard actions" + }, + "close": { + "description": "Close", + "message": "Close" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "Block Unwanted Cookies:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad / malware domains; below mode applies to this as well", + "message": "blocks cookies from known ad / malware domains; below mode applies to this as well" + }, + "custom": { + "description": "Custom", + "message": "Custom" + }, + "default": { + "description": "Default", + "message": "Default" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "Protect Local IP" + }, + "deny": { + "description": "Deny", + "message": "Deny" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "Protect Local and Public IPs" + }, + "disable": { + "description": "Disable", + "message": "Disable" + }, + "disabled": { + "description": "disabled", + "message": "disabled" + }, + "disabledcap": { + "description": "Disabled", + "message": "Disabled" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary)." + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "Disable and Remove:" + }, + "distrust": { + "description": "Distrust", + "message": "Distrust" + }, + "distrustlow": { + "description": "distrust", + "message": "distrust" + }, + "domain": { + "description": "Same Domain", + "message": "Same Domain" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "Sort by Domain:" + }, + "domaininfo": { + "description": "Help", + "message": "Help" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "Invalid domain/address" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "The domain or address must contain some letters/numbers" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see \"Block Unwanted Content\" and/or \"Antisocial Mode\")", + "message": "Domain cannot be added as it is a provider of unwanted content (see \"Block Unwanted Content\" and/or \"Antisocial Mode\")" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "sorts URL lists by domains on this page and in the panel" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "Tip: press CTRL+F to search the lists" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "Don't Show This Page Again" + }, + "enable": { + "description": "Enable:", + "message": "Enable:" + }, + "enabled": { + "description": "enabled", + "message": "enabled" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "Enable ScriptSafe" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "Enable Syncing:" + }, + "export": { + "description": "Export", + "message": "Export" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "Your settings have been successfully synced!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "Settings Synced!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "Fingerprint Protection" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "Fingerprint Protection (may break sites)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message." + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour." + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "Do you want to import the synced settings from your Google Account to this device?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "Hotkeys:" + }, + "generalsettings": { + "description": "General Settings", + "message": "General Settings" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "Block Gamepad Enumeration:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "prevent having devices detected via the Gamepad API" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "Available hotkey actions" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "Temporarily allow/block all resources for a current tab" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "Remove temporary permissions for a current tab" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "Remove all temporary permissions" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "Configure ScriptSafe hotkeys" + }, + "hotkeysinst": { + "description": "click on \"Keyboard Shortcuts\"", + "message": "click on \"Keyboard Shortcuts\"" + }, + "listallsettings": { + "description": "List All Settings", + "message": "List All Settings" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "Ignored Allow" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "The latest settings have been successfully downloaded!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "Settings Downloaded!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "Group All Settings" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "Remove Possible Hash Tracking:" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "Hide" + }, + "import": { + "description": "Import", + "message": "Import" + }, + "importexport": { + "description": "Import / Restore Settings", + "message": "Import / Restore Settings" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "Settings imported successfully" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "Settings imported successfully, except the following (empty value or unrecognized name): " + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "Settings imported successfully and syncing in 10 seconds" + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "Reduce Keyboard Fingerprinting (for advanced users):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behaviour:", + "message": "Page Link Opening Behaviour:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "modifies how all links are opened" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "Loose - allow same domain and subdomains" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "Default Mode" + }, + "newtab": { + "description": "New Tab", + "message": "New Tab" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "This tab has loaded no external resources" + }, + "notfiltered": { + "description": "Not filtered", + "message": "Not filtered" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "This browser does not support WebRTC protection" + }, + "off": { + "description": "-Off-", + "message": "-Off-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "Only on Unwhitelisted Domains" + }, + "options": { + "description": "Options", + "message": "Options" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "Paranoia Mode:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "block allowed domains on unlisted domains" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "Paste in settings and try again" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "Respect Same-Domain:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "preserve same-domain elements" + }, + "privacy": { + "description": "Privacy Settings", + "message": "Privacy Settings" + }, + "random": { + "description": "Random", + "message": "Random" + }, + "rating": { + "description": "Show Rating Button:", + "message": "Show Rating Button:" + }, + "ratingbtn": { + "description": "Rating", + "message": "Rating" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "if ticked, adds rating button under domains in tab popup" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work." + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "Block Click-Through Referrer:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to \"On All Domains\" may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "blocks referrer information when clicking on third-party links (note: setting this to \"On All Domains\" may cause issues (e.g. thumbnails in Tweetdeck))" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "Referrer Spoof:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may \"break\" some sites (e.g. logging in)", + "message": "warning: if enabled, may \"break\" some sites (e.g. logging in)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "Auto-Refresh Page:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "auto-refresh page after list change" + }, + "relaxed": { + "description": "Relaxed", + "message": "Relaxed" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "Revoke Page Temporary Permissions" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "Revoke All Temporary" + }, + "same": { + "description": "Same Document", + "message": "Same Document" + }, + "sametab": { + "description": "Same Tab", + "message": "Same Tab" + }, + "save": { + "description": "Save", + "message": "Save" + }, + "savetxt": { + "description": "Save as Text File", + "message": "Save as Text File" + }, + "sections": { + "description": "Sections", + "message": "Sections" + }, + "settingsall": { + "description": "select all", + "message": "select all" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button." + }, + "settingssave": { + "description": "Settings saved", + "message": "Settings saved" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "Settings saved and syncing in 10 seconds" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "Show in Context Menu:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafe is disabled" + }, + "strict": { + "description": "Strict", + "message": "Strict" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "Strict - allow same domain only" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "To support development, click the heart :)" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on \"Sync Settings FROM Google Account\".", + "message": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on \"Sync Settings FROM Google Account\"." + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "Sync Settings FROM Google Account" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "Sync Settings TO Google Account" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account." + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary)." + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "Show Import Sync Notification:" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": "show popup when settings synced from your Google Account" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "Show Sync Notification:" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "show popup when settings synced to your Google Account" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again." + }, + "temp": { + "description": "Temporary", + "message": "Temporary" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "Spoof Timezone:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail." + }, + "trust": { + "description": "Trust", + "message": "Trust" + }, + "trustlow": { + "description": "trust", + "message": "trust" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "Apply to whitelisted domains as well" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside \"Show Update Popup\".", + "message": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside \"Show Update Popup\"." + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "Update notifications disabled" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "Show Changelog on Update:" + }, + "unwanted": { + "description": "Unwanted", + "message": "Unwanted" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "show changelog page when ScriptSafe is updated" + }, + "url": { + "description": "Domain", + "message": "Domain" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "Enter a domain or expression (click 'Help' for more info)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "User-Agent Spoof:" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "spoofs your user-agent (browser and OS)" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "Enter an address to set as your referrer value for all sites" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "Remove Google Analytics (UTM) Tracking:" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "remove Google Analytics (UTM) tracking tokens" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "Remove Webbugs:" + }, + "webbugsdesc": { + "description": "remove \"invisible\" third-party elements", + "message": "remove \"invisible\" third-party elements" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "Block WebGL Fingerprinting:" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "prevent fingerprinting via the WebGL API" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "Block Device Enumeration:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "prevent having hardware devices detected via the WebRTC API" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "WebRTC Protection:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "prevent IP address leakage" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "Block WebVR Enumeration:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "prevent having devices detected via the WebVR API" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ Blacklist" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ Whitelist" + }, + "blacklist": { + "description": "Blacklist", + "message": "Blacklist" + }, + "whitelist": { + "description": "Whitelist", + "message": "Whitelist" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "Blacklisted" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "Whitelisted" + }, + "blacklistlow": { + "description": "blacklist", + "message": "blacklist" + }, + "whitelistlow": { + "description": "whitelist", + "message": "whitelist" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "Move to Blacklist" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "Move to Whitelist" + }, + "whitelistblacklist": { + "description": "Whitelist / Blacklist", + "message": "Whitelist / Blacklist" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "XML HTTP Request Handling:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "Control All Requests" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "Control Cross-Domain Requests (allow Same-Domain)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "control XML HTTP Requests" + } +} \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/en_US/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/en_US/messages.json new file mode 100644 index 0000000..faa4b7a --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/en_US/messages.json @@ -0,0 +1,858 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "Regain control of the web and surf more securely." + }, + "alldomains": { + "description": "On All Domains", + "message": "On All Domains" + }, + "allow": { + "description": "Allow", + "message": "Allow" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "Allowed Items" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "Blocked Items" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "Allow All Blocked For Session" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "Block Unwanted Content:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "Unwanted Content Mode:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted" + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "Antisocial" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "Antisocial Mode:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "always remove social widgets/buttons, even if whitelisted" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site" + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "Block Audio Fingerprinting:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "prevent fingerprinting via the AudioContext API" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "Block Battery Fingerprinting:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "prevent fingerprinting via the Battery API" + }, + "behavior": { + "description": "Behavior Settings", + "message": "Behavior Settings" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "Block (recommended)" + }, + "block": { + "description": "Block", + "message": "Block" + }, + "blocked": { + "description": "Blocked", + "message": "Blocked" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "Block All Allowed For Session" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "Block Bluetooth Enumeration:" + }, + "bluetoothdesc": { + "description": "prevent having devices detected via the Bluetooth API", + "message": "prevent having devices detected via the Bluetooth API" + }, + "bulkimport": { + "description": "bulk import", + "message": "bulk import" + }, + "bulkbtn": { + "description": "Import to List", + "message": "Import to List" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "Bulk Import" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "Copy and paste domains into the box below. Each domain should be on a separate line." + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Canvas Fingerprint Protection:" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page.

Please try visiting a website.", + "message": "ScriptSafe cannot process this page.

Please try visiting a website." + }, + "canvasblank": { + "description": "Blank Readout", + "message": "Blank Readout" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "Random Readout" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "Completely Block Readout" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "protect against fingerprinting attempts through <canvas> elements" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "Block Canvas Font Access:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs." + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "Classic Options Mode:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "if ticked, closes tab options everytime an option is clicked" + }, + "clear": { + "description": "Clear", + "message": "Clear" + }, + "clearlow": { + "description": "clear", + "message": "clear" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "Block Client Rectangles:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns." + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "Prevent Clipboard Interference:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "prevent pages from interfering with clipboard actions" + }, + "close": { + "description": "Close", + "message": "Close" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "Block Unwanted Cookies:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad / malware domains; below mode applies to this as well", + "message": "blocks cookies from known ad / malware domains; below mode applies to this as well" + }, + "custom": { + "description": "Custom", + "message": "Custom" + }, + "default": { + "description": "Default", + "message": "Default" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "Protect Local IP" + }, + "deny": { + "description": "Deny", + "message": "Deny" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "Protect Local and Public IPs" + }, + "disable": { + "description": "Disable", + "message": "Disable" + }, + "disabled": { + "description": "disabled", + "message": "disabled" + }, + "disabledcap": { + "description": "Disabled", + "message": "Disabled" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary)." + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "Disable and Remove:" + }, + "distrust": { + "description": "Distrust", + "message": "Distrust" + }, + "distrustlow": { + "description": "distrust", + "message": "distrust" + }, + "domain": { + "description": "Same Domain", + "message": "Same Domain" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "Sort by Domain:" + }, + "domaininfo": { + "description": "Help", + "message": "Help" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "Invalid domain/address" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "The domain or address must contain some letters/numbers" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see \"Block Unwanted Content\" and/or \"Antisocial Mode\")", + "message": "Domain cannot be added as it is a provider of unwanted content (see \"Block Unwanted Content\" and/or \"Antisocial Mode\")" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "sorts URL lists by domains on this page and in the panel" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "Tip: press CTRL+F to search the lists" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "Don't Show This Page Again" + }, + "enable": { + "description": "Enable:", + "message": "Enable:" + }, + "enabled": { + "description": "enabled", + "message": "enabled" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "Enable ScriptSafe" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "Enable Syncing:" + }, + "export": { + "description": "Export", + "message": "Export" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "Your settings have been successfully synced!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "Settings Synced!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "Fingerprint Protection" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "Fingerprint Protection (may break sites)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message." + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour." + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "Do you want to import the synced settings from your Google Account to this device?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "Hotkeys:" + }, + "generalsettings": { + "description": "General Settings", + "message": "General Settings" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "Block Gamepad Enumeration:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "prevent having devices detected via the Gamepad API" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "Available hotkey actions" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "Temporarily allow/block all resources for a current tab" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "Remove temporary permissions for a current tab" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "Remove all temporary permissions" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "Configure ScriptSafe hotkeys" + }, + "hotkeysinst": { + "description": "click on \"Keyboard Shortcuts\"", + "message": "click on \"Keyboard Shortcuts\"" + }, + "listallsettings": { + "description": "List All Settings", + "message": "List All Settings" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "Ignored Allow" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "The latest settings have been successfully downloaded!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "Settings Downloaded!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "Group All Settings" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "Remove Possible Hash Tracking:" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "Hide" + }, + "import": { + "description": "Import", + "message": "Import" + }, + "importexport": { + "description": "Import / Restore Settings", + "message": "Import / Restore Settings" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "Settings imported successfully" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "Settings imported successfully, except the following (empty value or unrecognized name):" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "Settings imported successfully and syncing in 10 seconds" + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "Reduce Keyboard Fingerprinting (for advanced users):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behavior:", + "message": "Page Link Opening Behavior:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "modifies how all links are opened" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "Loose - allow same domain and subdomains" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "Default Mode" + }, + "newtab": { + "description": "New Tab", + "message": "New Tab" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "This tab has loaded no external resources" + }, + "notfiltered": { + "description": "Not filtered", + "message": "Not filtered" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "This browser does not support WebRTC protection" + }, + "off": { + "description": "-Off-", + "message": "-Off-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "Only on Unwhitelisted Domains" + }, + "options": { + "description": "Options", + "message": "Options" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "Paranoia Mode:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "block allowed domains on unlisted domains" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "Paste in settings and try again" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "Respect Same-Domain:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "preserve same-domain elements" + }, + "privacy": { + "description": "Privacy Settings", + "message": "Privacy Settings" + }, + "random": { + "description": "Random", + "message": "Random" + }, + "rating": { + "description": "Show Rating Button:", + "message": "Show Rating Button:" + }, + "ratingbtn": { + "description": "Rating", + "message": "Rating" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "if ticked, adds rating button under domains in tab popup" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work." + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "Block Click-Through Referrer:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to \"On All Domains\" may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "blocks referrer information when clicking on third-party links (note: setting this to \"On All Domains\" may cause issues (e.g. thumbnails in Tweetdeck))" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "Referrer Spoof:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may \"break\" some sites (e.g. logging in)", + "message": "warning: if enabled, may \"break\" some sites (e.g. logging in)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "Auto-Refresh Page:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "auto-refresh page after list change" + }, + "relaxed": { + "description": "Relaxed", + "message": "Relaxed" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "Revoke Page Temporary Permissions" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "Revoke All Temporary" + }, + "same": { + "description": "Same Document", + "message": "Same Document" + }, + "sametab": { + "description": "Same Tab", + "message": "Same Tab" + }, + "save": { + "description": "Save", + "message": "Save" + }, + "savetxt": { + "description": "Save as Text File", + "message": "Save as Text File" + }, + "sections": { + "description": "Sections", + "message": "Sections" + }, + "settingsall": { + "description": "select all", + "message": "select all" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button." + }, + "settingssave": { + "description": "Settings saved", + "message": "Settings saved" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "Settings saved and syncing in 10 seconds" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "Show in Context Menu:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafe is disabled" + }, + "strict": { + "description": "Strict", + "message": "Strict" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "Strict - allow same domain only" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "To support development, click the heart :)" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on \"Sync Settings FROM Google Account\".", + "message": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on \"Sync Settings FROM Google Account\"." + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "Sync Settings FROM Google Account" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "Sync Settings TO Google Account" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account." + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary)." + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "Show Import Sync Notification:" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": "show popup when settings synced from your Google Account" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "Show Sync Notification:" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "show popup when settings synced to your Google Account" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again." + }, + "temp": { + "description": "Temporary", + "message": "Temporary" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "Spoof Timezone:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail." + }, + "trust": { + "description": "Trust", + "message": "Trust" + }, + "trustlow": { + "description": "trust", + "message": "trust" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "Apply to whitelisted domains as well" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside \"Show Update Popup\".", + "message": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside \"Show Update Popup\"." + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "Update notifications disabled" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "Show Changelog on Update:" + }, + "unwanted": { + "description": "Unwanted", + "message": "Unwanted" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "show changelog page when ScriptSafe is updated" + }, + "url": { + "description": "Domain", + "message": "Domain" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "Enter a domain or expression (click 'Help' for more info)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "User-Agent Spoof:" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "spoofs your user-agent (browser and OS)" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "Enter an address to set as your referrer value for all sites" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "Remove Google Analytics (UTM) Tracking:" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "remove Google Analytics (UTM) tracking tokens" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "Remove Webbugs:" + }, + "webbugsdesc": { + "description": "remove \"invisible\" third-party elements", + "message": "remove \"invisible\" third-party elements" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "Block WebGL Fingerprinting:" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "prevent fingerprinting via the WebGL API" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "Block Device Enumeration:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "prevent having hardware devices detected via the WebRTC API" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "WebRTC Protection:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "prevent IP address leakage" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "Block WebVR Enumeration:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "prevent having devices detected via the WebVR API" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ Blacklist" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ Whitelist" + }, + "blacklist": { + "description": "Blacklist", + "message": "Blacklist" + }, + "whitelist": { + "description": "Whitelist", + "message": "Whitelist" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "Blacklisted" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "Whitelisted" + }, + "blacklistlow": { + "description": "blacklist", + "message": "blacklist" + }, + "whitelistlow": { + "description": "whitelist", + "message": "whitelist" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "Move to Blacklist" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "Move to Whitelist" + }, + "whitelistblacklist": { + "description": "Whitelist / Blacklist", + "message": "Whitelist / Blacklist" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "XML HTTP Request Handling:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "Control All Requests" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "Control Cross-Domain Requests (allow Same-Domain)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "control XML HTTP Requests" + } +} diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/es/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/es/messages.json new file mode 100644 index 0000000..c320263 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/es/messages.json @@ -0,0 +1,858 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "Recupere el control de la web y navegue de forma más segura." + }, + "alldomains": { + "description": "On All Domains", + "message": "En todos los dominios" + }, + "allow": { + "description": "Allow", + "message": "Permitir" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "Elementos Permitidos" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "Elementos Bloqueados" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "Permitir todos los elementos bloqueados para ésta Sesión" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "Bloquear contenido no deseado:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "eliminar el contenido no deseado de dominios conocidos con ad / malware; dominios enlistados en MVPS HOSTS, hpHOSTS (servidores de anuncios / rastreo), HOSTS Proyecto HOSTS de Peter Lowe, MalwareDomainList.com" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "Modo de Contenido No Deseado:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "Relajado = los dominios permitidos no se bloquearán; Estricto = los dominios en la lista de No Deseados serán bloqueados, incluso si están en la Lista Blanca" + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "Antisocial" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "Modo Antisocial:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "siempre eliminar widgets / botones sociales, incluso si están en la Lista Blanca" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "Para un bloqueo más completo, echa un vistazo a Privacy Badger, Desconectar, Blur, y / o uBlock Origin con todas las listas de suscripción en sitio Fanboy" + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "Bloquear toma de huellas digitales de Audio:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "prevenir toma de huellas digitales a través de la API de AudioContext" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "Bloquear toma de huellas digitales a la Batería:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "prevenir toma de huellas digitales a través de la API de la Batería" + }, + "behavior": { + "description": "Behaviour Settings", + "message": "Ajustes de Comportamiento" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "Bloquear (recomendado)" + }, + "block": { + "description": "Block", + "message": "Bloquear" + }, + "blocked": { + "description": "Blocked", + "message": "Bloqueado" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "Bloquear todos los permitidos en la Sesión" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "Bloquear Enumeración Bluetooth:" + }, + "bluetoothdesc": { + "description": "prevent having devices detected via the Bluetooth API", + "message": "prevenir detección de dispositivos a través de la API Bluetooth" + }, + "bulkimport": { + "description": "bulk import", + "message": "importación por lotes" + }, + "bulkbtn": { + "description": "Import to List", + "message": "Importar a la Lista" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "Importación por Lotes" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "Copiar y pegar los dominios en el cuadro de abajo. Cada dominio debe estar en una línea separada." + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Protección contra toma de huella digital al Canvas del navegador:" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page.

Please try visiting a website.", + "message": "ScriptSafe no puede procesar esta página.

Por favor, intente visitando un sitio web." + }, + "canvasblank": { + "description": "Blank Readout", + "message": "Lectura en blanco" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "Lectura Aleatoria" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "Bloquear lectura completamente" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "proteger contra intentos de toma de huellas digitales a través de elementos del <canvas> del navegador" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "Bloquear acceso a fuente del Canvas del navegador:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "prevenir que las fuentes del sistema sean enumeradas a través de elementos <canvas>. Puede interferir con Google Docs." + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "Opciones de Modo Clásico:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "si está marcada, cierra las opciones de pestaña cada vez que se hace click en una opción" + }, + "clear": { + "description": "Clear", + "message": "Borrar" + }, + "clearlow": { + "description": "clear", + "message": "borrar" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "Bloquear Rectángulos del Cliente:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "evitar toma de huellas digitales a través del cálculo de elementos rectángulares. Puede interferir con algunos menús desplegables." + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "Prevenir Interferencia al Portapapeles:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "prevenir que las páginas interferieran con acciones del portapapeles" + }, + "close": { + "description": "Close", + "message": "Cerrar" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "Bloquear Cookies no Deseadas:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad / malware domains; below mode applies to this as well", + "message": "bloquea cookies de dominios ad / malware conocidos; el modo de abajo se aplica a esto también" + }, + "custom": { + "description": "Custom", + "message": "Personalizado" + }, + "default": { + "description": "Default", + "message": "Valor por Defecto" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "Proteger IP Local" + }, + "deny": { + "description": "Deny", + "message": "Denegar" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "Proteger direcciónes IP Local y Pública" + }, + "disable": { + "description": "Disable", + "message": "Deshabilitar" + }, + "disabled": { + "description": "disabled", + "message": "deshabilitado" + }, + "disabledcap": { + "description": "Disabled", + "message": "Deshabilitado" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Sincronización deshabilitada.\r\nVea la página de opciones en cualquier momento para sincronizar su configuración (hacer una copia de seguridad de sus ajustes, si es necesario)." + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "Deshabilitar y Quitar:" + }, + "distrust": { + "description": "Distrust", + "message": "Desconfiar" + }, + "distrustlow": { + "description": "distrust", + "message": "desconfiar" + }, + "domain": { + "description": "Same Domain", + "message": "Mismo Dominio" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "Ordenar por Dominio:" + }, + "domaininfo": { + "description": "Help", + "message": "Ayuda" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "Dominio/Dirección no válida" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "El dominio o dirección deben contener algunas letras / números" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see Block Unwanted Content and/or Antisocial Mode)", + "message": "El Dominio no puede ser agregado, ya que es un proveedor de contenidos no deseados (vea Bloquear Contenido No Deseado y / o Modo Antisocial)" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "ordena las listas de URL por dominio en esta página y en el panel" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "Consejo: pulse CTRL + F para buscar en las listas" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "No Mostrar esta Página Nuevamente" + }, + "enable": { + "description": "Enable:", + "message": "Habilitar:" + }, + "enabled": { + "description": "enabled", + "message": "habilitado" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "Activar ScriptSafe" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "Activar sincronización:" + }, + "export": { + "description": "Export", + "message": "Exportar" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "Sus ajustes se han sincronizado correctamente!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "Ajustes Sincronizados!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "Protección de toma de Huellas Digitales" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "Protección de toma de Huellas Digitales (puede estropear sitios)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "Parece que no ha sincronizado sus ajustes en su cuenta de Google aún.\r\nScriptSafe está a punto de sincronizar la configuración actual en su cuenta de Google.\r\nHaga click en \"Aceptar\" si desea continuar.\r\nSi no, haga clic en \"Cancelar\", y en otro dispositivo con la configuración de su preferencia, actualice ScriptSafe y haga click en Aceptar cuando se le presente este mensaje." + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "¿Desea sincronizar la configuración actual en su cuenta de Google?\r\n Nota: por favor, no lo haga con frecuencia; hay un límite de 10 por minuto y 1000 por hora." + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "¿Desea importar los ajustes sincronizados desde su cuenta de Google a este dispositivo?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "Atajos de teclado:" + }, + "generalsettings": { + "description": "General Settings", + "message": "Configuración General" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "Bloquear Enumeración de Gamepad:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "prevenir detección de dispositivos a través de la API Gamepad" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "Atajos de teclado disponibles" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "Permitir temporalmente / bloquear todos los recursos para una pestaña" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "Eliminar permisos temporales para una pestaña" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "Eliminar todos los permisos temporales" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "Configurar atajos de teclado de ScriptSafe" + }, + "hotkeysinst": { + "description": "click on Keyboard Shortcuts", + "message": "haga click en Atajos de Teclado" + }, + "listallsettings": { + "description": "List All Settings", + "message": "Configuración Detallada" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "Permitir Ignorado" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "Los últimos ajustes se han descargado con éxito!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "Ajustes Descargados!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "Resumen de Configuración" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "Remover posible rastreo de Hashes:" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "eliminar posibles fichas de seguimiento creados mediante encripción hash, donde haya un atributo y valor (por ejemplo, #xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "Ocultar" + }, + "import": { + "description": "Import", + "message": "Importar" + }, + "importexport": { + "description": "Import / Restore Settings", + "message": "Importar / Restaurar Ajustes" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "Ajustes importados correctamente" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "Ajustes importados correctamente, excepto los siguientes (valor vacío o nombre no reconocido):" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "Ajustes importados correctamente para sincronizarse en 10 segundos" + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "Reducir toma de Duellas Digitales sobre Pulsaciones de Teclado (para usuarios avanzados):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "hacer tiempos de pulsación de teclas más aleatorios para incrementar anonimato (Nota: se agrega un retardo aleatorio entre pulsaciones de teclas; deshabilite esta configuración si es inaceptable)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behaviour:", + "message": "Comportamiento Inicial de Enlaces en Página:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "modifica la forma de abrir enlaces" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "Loose - permitir mismo dominio y subdominios" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "Modo por Defecto" + }, + "newtab": { + "description": "New Tab", + "message": "Nueva Pestaña" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "Esta ficha no ha cargado elementos externos" + }, + "notfiltered": { + "description": "Not filtered", + "message": "No filtrada" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "Este navegador no es compatible con Protección WebRTC" + }, + "off": { + "description": "-Off-", + "message": "-Apagado-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "Sólo en Dominios fuera de la Lista Blanca" + }, + "options": { + "description": "Options", + "message": "Opciones" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "Modo Paranoia:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "bloquear dominios permitidos en dominios no listados" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "Pegue los ajustes y vuelva a intentarlo" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "Respetar Mismo-Dominio:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "conservar elementos de un mismo-dominio" + }, + "privacy": { + "description": "Privacy Settings", + "message": "Configuración de Privacidad" + }, + "random": { + "description": "Random", + "message": "Aleatorio" + }, + "rating": { + "description": "Show Rating Button:", + "message": "Mostrar Botón de Calificación:" + }, + "ratingbtn": { + "description": "Rating", + "message": "Calificación" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "si está marcado, agrega botón de calificación bajo los dominios en la pestaña emergente" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafe ha sido actualizado / recargado recientemente.

Tendrá que volver a cargar esta pestaña, crear una nueva pestaña o reiniciar su navegador para que ScriptSafe funcione." + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "Bloquear Click Patrocinado por un Sitio Referente:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to On All Domains may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "bloquea información de sitios referentes al hacer clic en enlaces de terceros (nota: activarlo en todos los dominios puede causar problemas (por ejemplo, con imágenes miniatura en Tweetdeck))" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "Ofuscar sitio referente:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may break some sites (e.g. logging in)", + "message": "advertencia: si está activado, puede estropear algunos sitios (por ejemplo, accesos con usuario / contraseña)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "Auto-Actualizar Página:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "auto-actualizar página después de cambios en la lista" + }, + "relaxed": { + "description": "Relaxed", + "message": "Relajado" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "Revocar Temporalmente Permisos De Página" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "Revocar Todos Temporalmente" + }, + "same": { + "description": "Same Document", + "message": "Mismo Documento" + }, + "sametab": { + "description": "Same Tab", + "message": "Misma Pestaña" + }, + "save": { + "description": "Save", + "message": "Guardar" + }, + "savetxt": { + "description": "Save as Text File", + "message": "Guardar como Archivo de Texto" + }, + "sections": { + "description": "Sections", + "message": "Secciones" + }, + "settingsall": { + "description": "select all", + "message": "seleccionar todo" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "Copie y pegue los ajustes que desee importar a ScriptSafe en esta caja de texto y haga clic en el botón Importar." + }, + "settingssave": { + "description": "Settings saved", + "message": "Configuración guardada" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "Configuración guardada y sincronización en 10 segundos" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "Mostrar en menú emergente:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafe está desactivado" + }, + "strict": { + "description": "Strict", + "message": "Estricto" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "Estricto - permitir únicamente mismo dominio" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "Para apoyar al desarrollador, haga clic en el corazón. :) ¡Gracias!" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on Sync Settings FROM Google Account.", + "message": "Ha habilitado la sincronización automática. Con el fin de prevenir cualquier pérdida de datos sincronizados previamente (si existen), por favor haga clic en Configuración de Sincronización de la cuenta de Google." + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "Configuración de Sincronización DESDE la cuenta de Google" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "Configuración de Sincronización HACIA la cuenta de Google" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "ScriptSafe ha detectado que usted ya tiene su configuración sincronizada en su cuenta de Google!\r\nHaga clic en \"Aceptar\" si desea importar la configuración desde su cuenta de Google." + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Sincronización desactivada para prevenir sobre escritura de datos ya sincronizados.\r\nVea la página de Opciones en cualquier momento para sincronizar la configuración (hacer una copia de seguridad de sus ajustes si es necesario)." + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "Mostrar Notificación al hacer Importación Sincronización:" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": "mostrar advertencia cuando sincronice su configuración desde su cuenta de Google" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "Mostrar Notificación de Sincronización:" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "mostrar advertencia cuando la configuración sea sincronizada hacia su cuenta de Google" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "Su versión actual de Google Chrome no es compatible con la configuración de sincronización. Por favor, intente actualizar su versión de Chrome y vuelva a intentarlo." + }, + "temp": { + "description": "Temporary", + "message": "Temporal" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "Ofuscar Zona Horaria:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "ofuscar o cambiar aleatoriamente su zona horaria. NOTA: si está activada, puede interferir con la respuesta a mensajes de correo electrónico en Gmail." + }, + "trust": { + "description": "Trust", + "message": "Confiar" + }, + "trustlow": { + "description": "trust", + "message": "confiar" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "Aplicar a dominios en la lista blanca también" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside Show Update Popup.", + "message": "¿Seguro que desea desactivar las Notificaciones de Actualizaciones como ésta?\r\nUsted podrá volver a habilitar las Notificaciones de Actualización en la página Opciones de ScriptSafe, marcando la casilla para Mostrar Notificaciones de Actualización." + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "Notificaciones de Actualización desactivadas" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "Mostrar Lista de Cambios en la Actualización:" + }, + "unwanted": { + "description": "Unwanted", + "message": "No Deseado" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "mostrar página de Lista de Cambios cuando ScriptSafe se actualice" + }, + "url": { + "description": "Domain", + "message": "Dominio" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "Ingrese un dominio o expresión regular (click en \"Ayuda\" para más información)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "Ofuscar identificación de navegador / sistema operativo (User-Agent):" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "ofusca la identificación de su navegador / sistema operativo (User-Agent)" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "Introduzca una dirección para establecerla como sitio de referencia para todos los sitios" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "Remover rastreo de Google Analytics (UTM):" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "eliminar fichas de seguimiento de Google Analytics (UTM)" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "Remover píxeles WebBugs:" + }, + "webbugsdesc": { + "description": "remove invisible third-party elements", + "message": "remover elementos invisibles de terceros" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "Bloquear toma de Huellas Digitales WebGL :" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "prevenir toma de Huellas Digitales a través de la API de WebGL" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "Bloquear Enumeración de Dispositivo:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "prevenir la detección de dispositivos de hardware a través de la API de WebRTC" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "Protección WebRTC:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "prevenir filtración de dirección IP" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "Bloquear Enumeración WebVR:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "prevenir detección de dispositivos a través de la API WebVR" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ Lista Negra" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ Lista Blanca" + }, + "blacklist": { + "description": "Blacklist", + "message": "Lista Negra" + }, + "whitelist": { + "description": "Whitelist", + "message": "Lista Blanca" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "En Lista Negra" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "En Lista Blanca" + }, + "blacklistlow": { + "description": "blacklist", + "message": "lista negra" + }, + "whitelistlow": { + "description": "whitelist", + "message": "lista blanca" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "Mover a la Lista Negra" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "Mover a la Lista Blanca" + }, + "whitelistblacklist": { + "description": "Whitelist / Blacklist", + "message": "Lista Blanca / Lista Negra" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "Manipulación de Solicitudes XML HTTP:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "Controlar Todas las Solicitudes" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "Controlar las Solicitudes de Dominios Cruzados (permitir Mismo-Dominio)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "controlar las solicitudes de HTTP XML" + } +} diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/fr/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/fr/messages.json new file mode 100644 index 0000000..f2086f2 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/fr/messages.json @@ -0,0 +1,858 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "Reprenez le contrôle du web et naviguez de façon plus sécurisée." + }, + "alldomains": { + "description": "On All Domains", + "message": "Sur tous les domaines" + }, + "allow": { + "description": "Allow", + "message": "Permettre" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "Éléments autorisés" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "Éléments bloqués" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "Autoriser tous les éléments bloqués pour cette session" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "Bloquer le contenu indésirable:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "supprimer le contenu publicitaire indésirable et les domaines malveillants; domaines recueillies à partir de MVPS HOSTS, de hpHOSTS (ad / tracking servers), de Peter Lowe's HOSTS Project et de MalwareDomainList.com" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "Mode du contenu indésiré:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "Détendu = les domaines de la liste blanche ne seront pas bloqués; Strict = les domaines de la liste des indésirables seront bloqués même s'ils sont sur la liste blanche" + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "Antisocial" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "Mode Antisocial:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "toujours retirer les widgets et les boutons sociaux, même s'ils sont sur la liste blanche" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "Pour un blocage plus complet, consultez Privacy Badger, Disconnect, Blur, et / ou uBlock Origin avec toutes les listes de souscription sur le site de Fanboy" + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "Bloquer le suivi web par l'audio:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "empêcher le suivi web par l'intermédiaire de l'API AudioContext" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "Bloquer le suivi web par la batterie:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "empêcher le suivi web par l'intermédiaire de l'API de la batterie" + }, + "behavior": { + "description": "Behaviour Settings", + "message": "Paramètres de fonctionnement" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "Bloquer (recommandé)" + }, + "block": { + "description": "Block", + "message": "Bloquer" + }, + "blocked": { + "description": "Blocked", + "message": "Bloqué" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "Bloquer tous les éléments autorisés pour la session" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "Bloquer l'énumération Bluetooth:" + }, + "bluetoothdesc": { + "description": "prevent having devices detected via the Bluetooth API", + "message": "éviter d'avoir les périphériques détectés via l'API Bluetooth" + }, + "bulkimport": { + "description": "bulk import", + "message": "Importation en bloc" + }, + "bulkbtn": { + "description": "Import to List", + "message": "Importer dans la liste" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "Importation en Bloc" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "Copier et coller des domaines dans la zone ci-dessous. Chaque domaine doit être sur une ligne distincte." + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Protection contre le suivi web avec les canvas:" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page.

Please try visiting a website.", + "message": "ScriptSafe ne peut pas traiter cette page.

S'il vous plaît, essayez de visiter un site Web." + }, + "canvasblank": { + "description": "Blank Readout", + "message": "Données vides" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "Données aléatoires" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "Bloquer complètement la lecture des données" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "protection contre le suivi web à l'aide des éléments <canvas>" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "Bloquer l'accès aux polices de caractères par les canvas:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "empêcher les polices systèmes d'être énumérées par <canvas> éléments. Peut interférer avec Google Docs." + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "Options du mode classique:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "si coché, ferme l'onglet d'options à chaque fois une option est cliqué" + }, + "clear": { + "description": "Clear", + "message": "Réinitialiser" + }, + "clearlow": { + "description": "clear", + "message": "réinitialiser" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "Bloquer les rectangles clients:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "éviter le suivi web en calculant les rectangles des éléments. Peut interférer avec quelques menus déroulants." + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "Empêcher l'interférence avec le presse-papier:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "empêcher les pages d'interférer avec les actions du presse-papier" + }, + "close": { + "description": "Close", + "message": "Fermer" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "Bloquer les cookies indésirables:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad / malware domains; below mode applies to this as well", + "message": "bloque les cookies des domaines publicitaires / malveillants; le mode ci-dessous s'applique aussi à cette option" + }, + "custom": { + "description": "Custom", + "message": "Personnalisé" + }, + "default": { + "description": "Default", + "message": "Défaut" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "Protéger l'IP locale" + }, + "deny": { + "description": "Deny", + "message": "Refuser" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "Protéger les IP locales et publiques" + }, + "disable": { + "description": "Disable", + "message": "Désactiver" + }, + "disabled": { + "description": "disabled", + "message": "désactivé" + }, + "disabledcap": { + "description": "Disabled", + "message": "Désactivé" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "La synchronisation est désactivée.\r\nVous pouvez vous rendre à la page d'options à tout moment pour synchroniser vos paramètres (faites une sauvegarde de vos paramètres si nécessaire)." + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "Désactiver et supprimer:" + }, + "distrust": { + "description": "Distrust", + "message": "Se méfier" + }, + "distrustlow": { + "description": "distrust", + "message": "se méfier" + }, + "domain": { + "description": "Same Domain", + "message": "Même Domaine" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "Trier par Domaine:" + }, + "domaininfo": { + "description": "Help", + "message": "Aidez-moi" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "Domaine/adresse non valide" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "Le nom de domaine ou l'adresse doivent contenir des lettres / chiffres" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see Block Unwanted Content and/or Antisocial Mode)", + "message": "Le domaine ne peut pas être ajouté car il est un fournisseur de contenus non désirés (voir le blocage de contenu indésirable et / ou le mode Antisocial)" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "trie les listes d'URL par domaines sur cette page et dans le panneau" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "Astuce: appuyez sur CTRL + F pour rechercher dans les listes" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "Ne plus afficher cette page" + }, + "enable": { + "description": "Enable:", + "message": "Activer:" + }, + "enabled": { + "description": "enabled", + "message": "activé" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "Activer ScriptSafe" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "Activer la synchronisation:" + }, + "export": { + "description": "Export", + "message": "Exporter" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "Vos paramètres ont été synchronisés avec succès!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "Paramètres synchronisés!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "Protection contre le suivi web" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "Protection contre le suivi web (peut briser des sites)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "Il semble que vous n'avez pas encore synchronisé vos paramètres à votre compte Google.\r\nScriptSafe est sur le point de synchroniser vos paramètres actuels à votre compte Google.\r\nCliquez sur 'OK' si vous voulez continuer.\r\nSinon, cliquez sur «Annuler», et sur l'autre appareil avec vos paramètres préférés, mettez à jour ScriptSafe et cliquez sur OK lorsqu'on vous présente ce message." + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "Est-ce que vous souhaitez synchroniser vos paramètres actuels à votre compte Google\r\nNote: s'il vous plaît ne pas appuyer trop fréquemment; il y a une limite de 10 par minute et 1000 par heure." + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "Est-ce que vous voulez importer les paramètres synchronisés à partir de votre compte Google pour cet appareil?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "Raccourcis:" + }, + "generalsettings": { + "description": "General Settings", + "message": "Paramètres généraux" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "Bloquer l'énumération des manettes de jeu:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "éviter d'avoir les périphériques détectés via l'API Gamepad" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "Actions de raccourcis disponibles" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "Autoriser / Bloquer temporairement toutes les ressources pour un onglet en cours" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "Supprimer les autorisations temporaires pour un onglet en cours" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "Supprimer toutes les autorisations temporaires" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "Configurer les raccourcis de ScriptSafe" + }, + "hotkeysinst": { + "description": "click on Keyboard Shortcuts", + "message": "cliquez sur raccourcis clavier" + }, + "listallsettings": { + "description": "List All Settings", + "message": "Afficher tous les paramètres" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "Ignoré Autoriser" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "Les derniers réglages ont été téléchargés avec succès!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "Réglages téléchargés!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "Grouper tous les paramètres" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "Retirer le suivi avec les méthodes de hachage:" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "retirer les jetons de suivi possibles passés à l'aide de hachage, où il y a un attribut et une valeur (par exemple #xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "Cacher" + }, + "import": { + "description": "Import", + "message": "Importer" + }, + "importexport": { + "description": "Import / Restore Settings", + "message": "Importation / Restauration des paramètres" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "Paramètres importés avec succès" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "Paramètres importés avec succès, à l'exception de la (valeur vide ou un nom non reconnu) suivant:" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "Paramètres importés avec succès et la synchronisation en 10 secondes" + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "Réduire le suivi clavier (pour les utilisateurs avancés):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "rend la fréquence de la saisie plus aléatoire pour augmenter l'anonymat (NOTE: ajoute un délai aléatoire entre la frappe des touches, désactiver ce paramètre si inacceptable)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behaviour:", + "message": "Comportement d'ouverture de liens:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "modifie la façon dont tous les liens sont ouverts" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "Relâché - permettent même domaine et sous-domaines" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "Mode par défaut" + }, + "newtab": { + "description": "New Tab", + "message": "Nouvel onglet" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "Cet onglet a été chargé sans ressources externes" + }, + "notfiltered": { + "description": "Not filtered", + "message": "Non filtré" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "Ce navigateur ne supporte pas la protection WebRTC" + }, + "off": { + "description": "-Off-", + "message": "-Désactivé-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "Uniquement sur les domaines ne figurant pas sur la liste blanche" + }, + "options": { + "description": "Options", + "message": "Options" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "Mode Paranoia:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "bloquer les domaines autorisés sur les domaines ne figurant pas sur la liste blanche" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "Collez les paramètres et essayez à nouveau" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "Respect du Même Domaine:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "préserver les éléments du même domaine" + }, + "privacy": { + "description": "Privacy Settings", + "message": "Paramètres de confidentialité" + }, + "random": { + "description": "Random", + "message": "Aléatoire" + }, + "rating": { + "description": "Show Rating Button:", + "message": "Afficher le bouton d'évaluation:" + }, + "ratingbtn": { + "description": "Rating", + "message": "Évaluation" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "si cochée, ajoute le bouton d'évaluation sous les domaines dans l'onglet contextuel" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafe a été récemment mis à jour / rechargé.

Vous devrez soit rafraîchir cet onglet, créer un nouvel onglet, ou redémarrer votre navigateur pour que ScriptSafe fonctionne." + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "Bloquer les informations de référent:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to On All Domains may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "bloque les informations du site référent en cliquant sur les liens tiers (note: mettre cette option sur Tous les domaines peut provoquer des problèmes (par exemple les vignettes dans Tweetdeck))" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "Cacher le référent:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may break some sites (e.g. logging in)", + "message": "avertissement: si activé, peut briser certains sites (par exemple: se connecter)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "Rafraichir automatiquement:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "Rafraichir automatiquement après un changement de la liste" + }, + "relaxed": { + "description": "Relaxed", + "message": "Relâché" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "Révoquer les autorisations temporaires de la page" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "Révoquer toutes les autorisations temporaires" + }, + "same": { + "description": "Same Document", + "message": "Même document" + }, + "sametab": { + "description": "Same Tab", + "message": "Même onglet" + }, + "save": { + "description": "Save", + "message": "Sauvegarder" + }, + "savetxt": { + "description": "Save as Text File", + "message": "Enregistrer comme fichier texte" + }, + "sections": { + "description": "Sections", + "message": "Sections" + }, + "settingsall": { + "description": "select all", + "message": "sélectionner tout" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "Copiez et collez les paramètres que vous souhaitez importer dans ScriptSafe dans cette case puis cliquez sur le bouton Importer." + }, + "settingssave": { + "description": "Settings saved", + "message": "Paramètres sauvegardés" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "Réglages sauvegardés et synchronisation dans 10 secondes" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "Show in Context Menu:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafe est désactivé" + }, + "strict": { + "description": "Strict", + "message": "Strict" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "Strict - permettre même domaine seulement" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "Pour soutenir le développement, cliquez sur le coeur :)" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on Sync Settings FROM Google Account.", + "message": "Vous avez activé l'auto-synchronisation. Afin d'éviter l'effacement de vos données précédemment synchronisées (dans le cas échéant), s'il vous plaît cliquer sur synchronisation à partir du compte Google." + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "Synchronisation à partir du compte Google" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "Synchronisation vers le compte Google" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "ScriptSafe a détecté que vous avez des paramètres synchronisés sur votre compte Google!\r\nCliquez sur 'OK' si vous voulez importer les paramètres de votre compte Google." + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "La synchronisation a été désactivée pour éviter d'écraser vos données déjà synchronisées.\r\nAllez à la page Options à tout moment pour synchroniser vos paramètres (faites une sauvegarde de vos paramètres si nécessaire)." + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "Afficher les notifications de synchronisation (importation):" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": "montrer le popup lorsque les réglages sont synchronisés à partir de votre compte Google" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "Afficher les notifications de synchronisation (exportation):" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "montrer le popup lorsque les réglages sont synchronisés vers votre compte Google" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "Votre version actuelle de Google Chrome ne prend pas en charge la synchronisation des paramètres. S'il vous plaît, mettez à jour votre version Chrome et essayez à nouveau." + }, + "temp": { + "description": "Temporary", + "message": "Temporaire" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "Cacher le fuseau horaire:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "Cacher votre fuseau horaire. REMARQUE: cela peut interférer avec la réponse à des e-mails dans Gmail." + }, + "trust": { + "description": "Trust", + "message": "Faire confiance" + }, + "trustlow": { + "description": "trust", + "message": "faire confiance" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "S'applique aussi aux domaines de la liste blanche" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside Show Update Popup.", + "message": "Êtes-vous sûr de vouloir désactiver les futures notifications de mise à jour comme celui-ci d'apparaître?\r\nVous peut toujours re-autoriser les notifications de mise à jour en allant à la page Options ScriptSafe et en cochant la case Afficher Update Popup." + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "Notifications de mise à jour désactivées" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "Afficher la liste de changements lors des mises à jours:" + }, + "unwanted": { + "description": "Unwanted", + "message": "Indésirable" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "afficher la page de changelog lorsque ScriptSafe est mis à jour" + }, + "url": { + "description": "Domain", + "message": "Domaine" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "Entrez un nom de domaine ou une expression (cliquez sur «Aide» pour plus d'info)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "Cacher l'agent utilisateur:" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "cache votre agent utilisateur (navigateur et système d'exploitation)" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "Entrez une adresse à définir comme référent pour tous les sites" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "Supprimer le suivi de Google Analytics (UTM):" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "supprimer les jetons de suivi Google Analytics (UTM)" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "Retirer les Webbugs:" + }, + "webbugsdesc": { + "description": "remove invisible third-party elements", + "message": "supprimer les éléments invisibles tiers" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "Bloquer le suivi avec WebGL:" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "éviter le suivi web via l'API WebGL" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "Bloquer l'énumération des périphériques:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "éviter d'avoir les périphériques matériels détectés via l'API WebRTC" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "Protection WebRTC:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "empêcher la fuite de l'adresse IP" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "Bloquer l'énumération WebVR:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "éviter d'avoir les périphériques détectés via l'API WebVR" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ Liste noire" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ Liste blanche" + }, + "blacklist": { + "description": "Blacklist", + "message": "Liste noire" + }, + "whitelist": { + "description": "Whitelist", + "message": "Liste blanche" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "Sur la liste noire" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "Sur la liste blanche" + }, + "blacklistlow": { + "description": "blacklist", + "message": "liste noire" + }, + "whitelistlow": { + "description": "whitelist", + "message": "liste blanche" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "Déplacer vers la liste noire" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "Déplacer vers la liste blanche" + }, + "whitelistblacklist": { + "description": "Whitelist / Blacklist", + "message": "Liste blanche / Liste noire" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "Traitement des requêtes XML HTTP:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "Contrôle de toutes les requêtes" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "Les requêtes de contrôle inter-domaine (permettre même domaine)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "contrôle les requêtes HTTP XML" + } +} diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/hu/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/hu/messages.json new file mode 100644 index 0000000..8df3d54 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/hu/messages.json @@ -0,0 +1,858 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "Szerezd vissza az irányítást a web felett és böngéssz biztonságosan!" + }, + "alldomains": { + "description": "On All Domains", + "message": "Minden domainen" + }, + "allow": { + "description": "Allow", + "message": "Engedélyezés" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "Engedélyezett elemek" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "Blokkolt elemek" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "Mind engedélyezése ezen a munkameneten" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "Kéretlen tartalom blokkolása:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "Kéretlen tartalom eltávolítása adware/malware domainekről; domainek ezekről a helyekről vesszük: MVPS HOSTS, hpHOSTS ( ad / tracking szerverek), Peter Lowe HOSTS Projekt, MalwareDomainList.com" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "Kéretlen tartalom mód:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "Mérsékelt = fehérlistás domain-eket nem blokkolja; Szigorú = A kéretlen listán lévő domainek az engedélyezés ellenére is blokkolva lesznek." + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "Antiszociális" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "Antiszociális Mód:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "mindig távolítsa el a közzösségi oldalak beépülő moduljait, még akkor is, ha fehérlistázva lettek." + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "Átfogóbb blokkolóért, nézd meg a Privacy Badger, Disconnect a blur, és / vagy a uBlock Origin és az összes listákat fanboy site" + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "Audio fingerprinting blokkolása:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "Megakadályozza, hogy az AudioContext API-n keresztül azonosítsák a felhasználót" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "Akkumulátor Fingerprinting blokkolása:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "Megakadályozza, hogy Battery API-n keresztül azonosítsák a felhasználót." + }, + "behavior": { + "description": "Behaviour Settings", + "message": "Viselkedés beállításai" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "Blokkolás (ajánlott)" + }, + "block": { + "description": "Block", + "message": "Blokkolás" + }, + "blocked": { + "description": "Blocked", + "message": "Blokkolt" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "Ideiglenes jogosultságok visszavonása" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "Bluetooth enumeráció blokkolása:" + }, + "bluetoothdesc": { + "description": "prevent having devices detected via the Bluetooth API", + "message": "megakadályozza, hogy a Bluetooth API-n keresztül eszközöket érszékeljenek az oldalak." + }, + "bulkimport": { + "description": "bulk import", + "message": "tömeges import" + }, + "bulkbtn": { + "description": "Import to List", + "message": "Import to List" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "Tömeges importálás" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "Másolj domaineket ebbe a dobozba. Minden domain külön sorban legyen!" + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Canvas fingerprint védelem:" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page.

Please try visiting a website.", + "message": "ScriptSafe nem tudja feldolgozni ezt az oldalt.

Keressen fel egy weboldalt." + }, + "canvasblank": { + "description": "Blank Readout", + "message": "Üres kiolvasás" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "Véletlenszerű kiolvasás" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "Kiolvasás teljes blokkolása" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "felhasználó azonosításának megakadályozása <canvas> elemen keresztül" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "Canvas font elérésének a blokkolása:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "rendszerfontok listázásának megakadályozása a <canvas> elemeken keresztül. (Google Docs lehet, hogy nem fog működni emiatt) " + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "Klasszikus nézet:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "ha be van jelölve, bezárja lap beállításait minden alkalommal, amikor lehetőséget kattintanak" + }, + "clear": { + "description": "Clear", + "message": "Törlés" + }, + "clearlow": { + "description": "clear", + "message": "törlés" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "Kliens területek blokkolása:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "Azonosítás megakadályozása elem téglalap meghatározásán keresztül. Némelyik legördölő menünél problémát okozhat." + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "Vágólap piszkálásának megakadályozása:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "Megakadályozza, hogy az oldalak zavarják a vágólap működését" + }, + "close": { + "description": "Close", + "message": "Bezárás" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "Kéretlen sütik blokkolása:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad / malware domains; below mode applies to this as well", + "message": "Blokkolja a sütiket adware és malware oldalakról; az alábbi mód erre is vonatkozik" + }, + "custom": { + "description": "Custom", + "message": "Saját" + }, + "default": { + "description": "Default", + "message": "Alapértelmezett" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "Helyi IP védelme" + }, + "deny": { + "description": "Deny", + "message": "Letiltás" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "Helyi és nyilvános IP-k védelme" + }, + "disable": { + "description": "Disable", + "message": "Kikapcsolás" + }, + "disabled": { + "description": "disabled", + "message": "tiltva" + }, + "disabledcap": { + "description": "Disabled", + "message": "Tiltva" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "A szinkronizálás le van tiltva.\r\nA beállítások oldalon bármikor szinkronizálhatsz. (hogy egy biztonsági másolatot a beállításaidról, ha szükséges)." + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "Tiltás és eltávolítás:" + }, + "distrust": { + "description": "Distrust", + "message": "Bizalom megtagadása" + }, + "distrustlow": { + "description": "distrust", + "message": "bizalom megtagadása" + }, + "domain": { + "description": "Same Domain", + "message": "Ugyanaz a domain" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "Rendezés Domain Szerint:" + }, + "domaininfo": { + "description": "Help", + "message": "Súgó" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "Érvénytelen domain / cím" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "A domainnek vagy címnek tartalmaznia kell néhány karaktert" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see Block Unwanted Content and/or Antisocial Mode)", + "message": "A domaint nem lehet hozzáadni, mert kéretlen tartalmat szolgáltat (lásd a kéretlen tartalom blokkolása vagy antiszociális mód)" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "rendezi a URL listát domain szerint ezen az oldalon és ebben a panelben." + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "Tipp: Ctrl+F, hogy keress a listában" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "Ne mutasd ezt az oldalt mégegyszer." + }, + "enable": { + "description": "Enable:", + "message": "Engedélyezés:" + }, + "enabled": { + "description": "enabled", + "message": "engedélyezve" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "ScriptSafe engedélyezése" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "Szinkronizálás engedélyezése:" + }, + "export": { + "description": "Export", + "message": "Exportálás" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "A beállítások sikeresen szinkronizálva!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "Beállítások Szinkronizálva!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "Ujjlenyomat védelem" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "Ujjlenyomat védelem (néhány weboldalt elronthat)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "Úgy tunik, még nem szinkronizáltad a beállításaid a Google accountra.\r\nScriptSafe hamarosan szinkronizálja az aktuális beállításokat a Google-fiókjába.\r\nAz \"OK\", ha azt akarjuk, hogy továbbra is.\r\nHa nem kattintson a \"Mégse\" gombra, és azon az eszközön, amelyen a preferált beállítások vannak és kattints az OK-ra, amikor ez az üzenet megjelenik." + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "Szeretné szinkronizálni az aktuális beállításokat a Google-fiókjába?\r\nMegjegyzés: kérjük, ne nyomja meg ezt gyakran; mert pecenként 10-szer óránként 1000-szer lehet szinronizálni." + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "Szeretné importálni a szinkronizált beállításokat a Google-fiókból erre az eszközre?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "Gyorsbillentyűk:" + }, + "generalsettings": { + "description": "General Settings", + "message": "Általános Beállítások" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "Gamepad enumeráció blokkolása:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "megakadályozza, hogy a Gamepad API-n keresztül eszközöket érszékeljenek az oldalak." + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "Elérhető gyorsbillentyű műveletek" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "Minden engedélyezése/blokkolása ezen a lapon" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "Ideiglenes jogosultságok visszavonása az aktuális lapról" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "Minden ideiglenes jogosultság visszavonása" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "Scriptsafe gyorsbillentyűk beállítása" + }, + "hotkeysinst": { + "description": "click on Keyboard Shortcuts", + "message": "kattintson gyorsbillentyűkre" + }, + "listallsettings": { + "description": "List All Settings", + "message": "Minden beállítás listázása" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "Ignored Allow" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "Sikerült letölteni a legutobbi beállításokat!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "Beállítások letöltve!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "Csoport nézet" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "Hash tracking eltávolítása:" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "követő tokenek eltávolítása, amikor kulcs érték párként szerepelnek (például #xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "Elrejtés" + }, + "import": { + "description": "Import", + "message": "Importálás" + }, + "importexport": { + "description": "Import / Restore Settings", + "message": "Importálás / Beállítások Visszaállítása" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "Beállítások importálása sikeres" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "Beállítások importálása sikeres, kivéve az alábbi (üres érték, vagy fel nem ismert név):" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "Beállítások sikeresen importálva és 10 másodperc múlva szinkronizálva lesznek." + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "Billentyűzet fingerprinting csökkentése (Haladó felhasználók számára):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "billentyűlenyomások időközeinek randomizálása (Megjegyzés: hozzáad egy véletlenszerű szünetet a billentyű lenyomások után, tiltsd le ha zavar)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behaviour:", + "message": "Link nyitási viselkedés:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "Megadja, hogy hogyan nyissuk meg a linkeket" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "Laza - engedélyezi egy adott engedélyezett domain aldomainjeit is." + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "Alapértelmezett Mód" + }, + "newtab": { + "description": "New Tab", + "message": "Új Lap" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "Ez a lap semmilyen külső erőforrást nem használ" + }, + "notfiltered": { + "description": "Not filtered", + "message": "Nem szűrt" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "Ez a böngésző nem támogatja a WebRTC védelmet" + }, + "off": { + "description": "-Off-", + "message": "-Ki-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "Csak nem fehérlistás domaineken" + }, + "options": { + "description": "Options", + "message": "Lehetőségek" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "Paranoia Mód:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "engedélyezett domainek blokkolása nem listázott domaineken" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "Illeszd be a beállításokat, és próbáld újra" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "Azonos Domain tisztelenben tartása:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "azonos domainről származó elemek megtartása" + }, + "privacy": { + "description": "Privacy Settings", + "message": "Adatvédelmi Beállítások" + }, + "random": { + "description": "Random", + "message": "Véletlen" + }, + "rating": { + "description": "Show Rating Button:", + "message": "Értékelés gomb mutatása:" + }, + "ratingbtn": { + "description": "Rating", + "message": "Értékelés" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "ha be van jelölve, hozzáad egy értékelési gombot a domainekhez lap csoportban" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafe nemrég frissül / újratöltődött.

Frissítsd a lapot, vagy indítsd újra a böngészőt, hogy működjön a ScriptSafe." + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "Click-through referer blokkolása:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to On All Domains may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "referer blokkolása, amikor külső linkekre kattintanak (megjegyzés: ha mindenhol bekapcsolod, akkor problémát okozhat egyes helyeken (pl. Tweetdeck bélyegképek))" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "Referrer hamisítás:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may break some sites (e.g. logging in)", + "message": "figyelem: ha engedélyezve van, elromolhat néhány oldal (például bejelentkezés)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "Oldal automatikus frissítése:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "automatikusan frissül az oldal, hogyha változik valamelyik lista" + }, + "relaxed": { + "description": "Relaxed", + "message": "Mérsékelt" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "Oldal ideiglenes engedélyeinek visszavonása" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "Minden ideiglenes visszavonása" + }, + "same": { + "description": "Same Document", + "message": "Azonos Dokumentum" + }, + "sametab": { + "description": "Same Tab", + "message": "Ugyanaz a lap" + }, + "save": { + "description": "Save", + "message": "Mentés" + }, + "savetxt": { + "description": "Save as Text File", + "message": "Mentés szövegfájlként" + }, + "sections": { + "description": "Sections", + "message": "Szekciók" + }, + "settingsall": { + "description": "select all", + "message": "mind kiválasztása" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "Másolja be az importálni kívánt beállításokat ScriptSafe ebbe a mezőbe, majd kattintson az Importálás gombra." + }, + "settingssave": { + "description": "Settings saved", + "message": "Beállítások elmentve" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "Beállítások elmentve, szinkronizálás 10 másodpercen belül" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "Megjelenítés a Jobbgombos Menüben:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafe le van tiltva" + }, + "strict": { + "description": "Strict", + "message": "Szigorú" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "Szigorú - csak ugyanazon domainen" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "A fejlesztés támogatásának az érdekében kattints a szívre :)" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on Sync Settings FROM Google Account.", + "message": "Engedélyezted az automatikus szinkronizálást, ha el akarod kerülni, hogy a korábban szinkronziált beállítások felülíródjanak, először kattits a beéállítások letöltésére a Google-fiókból" + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "Beállítások letöltése a Google-fiókból" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "Beállítások feltöltése a Google-fiókba" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "ScriptSafe észlelte, hogy vannak már beállításaid feltöltve a Google-fiókodba!\r\nKattints az 'OK' gombra, ha importiálni szeretnéd a beállításokat a Google-fiókodból" + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "A szinkronizálás le van tiltva, hogy elkerüljük a korábban szinkronizált adatok felülírását\r\n. A Beállítások oldalán bármikor szinkronizálhatod őket." + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "Letöltési értesítés megjelenítése:" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": "felugró ablak mutatása, amikor letöltjük a beállításokat Google-fiókból" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "Feltöltési értesítés megjelenítése:" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "felugró ablak mutatása, amikor feltöltjük a beállításokat Google-fiókból" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "A jelenlegi Chrome verzió nem támogatja a beállítások szinkronizálását. Próbáld meg frissíteni a Chrome-osat, majd próbálkozz újra." + }, + "temp": { + "description": "Temporary", + "message": "Ideiglenes" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "Időzóna hamisítása:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "Hamisítja vagy véletlenszerűvé teszi az időzonádat. Megjegyzés: ha engedélyezve van, akkor bezavarhat, amikor e-mailekre válaszolsz a Gmailen." + }, + "trust": { + "description": "Trust", + "message": "Megbízuk benne" + }, + "trustlow": { + "description": "trust", + "message": "megbízuk benne" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "Alkalmazás az engedélyezett domainek esetén is" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside Show Update Popup.", + "message": "Biztos benne, hogy le akarja tiltani a jövőbeni értesítéseket?\r\nBármikor újra engedélyezheted, hogyha a Beállítások oldalán bepipálod a megfelelő mezőt." + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "Frissítési értesítések le vannak tiltva" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "Mutasd a Changelogot:" + }, + "unwanted": { + "description": "Unwanted", + "message": "Kéretlen" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "Changelog oldal megjelenítése, amikor a ScriptSafe frissül" + }, + "url": { + "description": "Domain", + "message": "Domain" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "Írd be a domaint vagy kifejezés (kattints a 'Súgó'-ra több információért)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "User-Agent hamisítás:" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "meghamísítja az User-Agent headert" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "Írd be a címet, amit majd refererként használunk minden oldalon." + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "Google Analytics (UTM) tracking eltávolítása:" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "Google Analytics (UTM) tracking tokenek eltávolítása" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "Webbugok eltávolítása:" + }, + "webbugsdesc": { + "description": "remove invisible third-party elements", + "message": "láthatatlan külső elemek eltávolítása" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "WebGL fingerprinting blokkolása:" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "Megakadályozza az azonosítást WebGL API-n keresztül" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "Eszközenumeráció blokkolása:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "Megakadályozza, hogy kilistázzák a hardvereket a WebRTC API-n keresztül" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "WebRTC Védelem:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "IP cím szivárgás megakadályozása" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "WebVR enumeráció blokkolása:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "megakadályozza, hogy a WebVR API-n keresztül eszközöket érszékeljenek az oldalak." + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ Tiltólista" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ Engedélyező lista" + }, + "blacklist": { + "description": "Blacklist", + "message": "Tiltólista" + }, + "whitelist": { + "description": "Whitelist", + "message": "Engedélyező lista" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "Tiltott" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "Engedélyezett" + }, + "blacklistlow": { + "description": "blacklist", + "message": "tiltólista" + }, + "whitelistlow": { + "description": "whitelist", + "message": "engedélyező lista" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "Áthelyezés a tiltólistára" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "Áthelyezés az engedélyező listára" + }, + "whitelistblacklist": { + "description": "Whitelist / Blacklist", + "message": "Engedélyező lista / Tiltólista" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "XML HTTP Kérés Kezelés:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "Összes lekérés irányítása" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "Cross-Domain lekérések kezelése (azonos domain engedélyezése)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "XML HTTP lekérések kezelése" + } +} diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/it/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/it/messages.json new file mode 100644 index 0000000..afeded0 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/it/messages.json @@ -0,0 +1,858 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "Riprendere il controllo del web e navigare in modo più sicuro." + }, + "alldomains": { + "description": "On All Domains", + "message": "Su Tutti i Domini" + }, + "allow": { + "description": "Allow", + "message": "Consentire" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "Elementi Consentiti" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "Elementi Bloccati" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "Consentire I Contenuti Bloccati Per Questa Sessione" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "Bloccare i Contenuti Indesiderati:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "Rimuovere I Contenuti Indesiderati Da Domini di AD/Malware Noti; Domini Raccolti Da MVP HOSTS, hpHOSTS ( ad server / tracking), Progetto Host di Peter Lowe, MalwareDomainList.com" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "Modalità Contenuto Indesiderato:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "Rilassata = I Domini Autorizzati Non Saranno Bloccati; Severa = I Domini Nella Lista Degli Indesiderati Verranno Bloccati Anche Se In Whitelist" + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "Antisocial" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "Modalità Antisocial:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "rimuovere sempre widgets / pulsanti social, anche se in whitelist" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "Per un blocco più completo, dai un'occhiata a Privacy Badger, Disconnetti, Blur, e / o uBlock Origin con tutte le liste di sottoscrizione sul sito Fanboy" + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "Blocco Audio Fingerprinting:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "prevenire le impronte digitali che usano l'API AudioContext" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "Blocco Batteria Fingerprinting:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "prevenire le impronte digitali che usano l'API della batteria" + }, + "behavior": { + "description": "Behaviour Settings", + "message": "Impostazioni di Comportamento" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "Blocca (consigliato)" + }, + "block": { + "description": "Block", + "message": "Bloccare" + }, + "blocked": { + "description": "Blocked", + "message": "Bloccato" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "Blocca Tutti I Consentiti Per Questa Sessione" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "Blocca Enumerazione del Bluetooth:" + }, + "bluetoothdesc": { + "description": "prevent having devices detected via the Bluetooth API", + "message": "previene la rilevazione dei vostri dispositivi tramite l'API del Bluetooth" + }, + "bulkimport": { + "description": "bulk import", + "message": "importazione di massa" + }, + "bulkbtn": { + "description": "Import to List", + "message": "Importa nell'elenco" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "Importazione di Massa" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "Copiare e incollare i domini nella casella sottostante. Ogni dominio deve essere su una riga separata." + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Protezione da Impronte Digitali di tipo Canvas:" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page.

Please try visiting a website.", + "message": "ScriptSafe non può elaborare questa pagina.

Si prega di provare a visitare un sito web." + }, + "canvasblank": { + "description": "Blank Readout", + "message": "Lettura in Bianco" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "Lettura Casuale" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "Lettura Completa del Blocco" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "protezione contro l'acquisizione di impronte digitali tramite elementi <canvas>" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "Blocca l'Accesso a Font Canvas:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "prevenire che i font di sistema siano enumerati tramite elementi <canvas>. Può interferire con Google Docs." + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "Modalità Opzioni Classica:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "se spuntata, chiude le opzioni della scheda ogni volta che un'opzione viene selezionata" + }, + "clear": { + "description": "Clear", + "message": "Pulire" + }, + "clearlow": { + "description": "clear", + "message": "pulire" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "Blocca Rettangoli Del Client:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "previene l'acquisizione di impronte digitali tramite il calcolo di elementi rettangolari. Può interferire con alcuni menù a discesa." + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "Prevenire Interferenze con la Clipboard:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "evita alle pagine di interferire con le azioni della clipboard" + }, + "close": { + "description": "Close", + "message": "Chiudi" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "Bloccare i Cookie Indesiderati:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad / malware domains; below mode applies to this as well", + "message": "blocca i cookie da domini noti di AD / malware; questo si applica anche alla modalità seguente" + }, + "custom": { + "description": "Custom", + "message": "Personalizzato" + }, + "default": { + "description": "Default", + "message": "Predefinito" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "Proteggi IP Locale" + }, + "deny": { + "description": "Deny", + "message": "Negare" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "Proteggere IP Locali e Pubblici" + }, + "disable": { + "description": "Disable", + "message": "Disabilitare" + }, + "disabled": { + "description": "disabled", + "message": "disabilitato" + }, + "disabledcap": { + "description": "Disabled", + "message": "Disabilitato" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "La sincronizzazione è disabilitata.\r\nSentitevi liberi di andare alla pagina delle opzioni in qualsiasi momento per sincronizzare le impostazioni (fare un backup delle impostazioni, se necessario)." + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "Disabilitare e Rimuovere:" + }, + "distrust": { + "description": "Distrust", + "message": "Non Fidarsi" + }, + "distrustlow": { + "description": "distrust", + "message": "non fidarsi" + }, + "domain": { + "description": "Same Domain", + "message": "Stesso Dominio" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "Ordina per Dominio:" + }, + "domaininfo": { + "description": "Help", + "message": "Aiuto" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "Dominio / indirizzo non valido" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "Il dominio o l'indirizzo devono contenere alcune lettere / numeri" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see Block Unwanted Content and/or Antisocial Mode)", + "message": "Il dominio non può essere aggiunto in quanto è un fornitore di contenuti indesiderati (vedi bloccare i contenuti indesiderati e / o modalità Antisocial)" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "ordina gli elenchi di URL per dominio in questa pagina e nel pannello" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "Suggerimento: premere CTRL + F per cercare le liste" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "Non Visualizzare Più Questa Pagina" + }, + "enable": { + "description": "Enable:", + "message": "Abilita:" + }, + "enabled": { + "description": "enabled", + "message": "abilitato" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "Abilita ScriptSafe" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "Abilita Sincronizzazione:" + }, + "export": { + "description": "Export", + "message": "Esportare" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "Le impostazioni sono state sincronizzate con successo!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "Impostazioni Sincronizzate!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "Protezione Delle Impronte Digitali" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "Protezione Impronte Digitali (potrebbe interromprere i siti)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "Sembra che non hai ancora sincronizzato le impostazioni con il tuo account Google.\r\nScriptSafe sta per sincronizzare le impostazioni correnti sul tuo account Google.\r\nCliccare su 'OK' se desidera continuare.\r\nSe no , fare clic su 'Annulla', e sull'altro dispositivo con le vostre impostazioni preferite, aggiornare ScriptSafe e cliccare su OK quando compare questo messaggio." + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "Volete sincronizzare le impostazioni correnti con il vostro account Google?\r\nNota: per favore non premere troppo frequentemente; c'è un limite di 10 al minuto e 1.000 all'ora." + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "Vuoi importare le impostazioni sincronizzate dal tuo account Google a questo dispositivo?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "Tasti di scelta rapida:" + }, + "generalsettings": { + "description": "General Settings", + "message": "Impostazioni Generali" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "Blocca Enumerazione del Gamepad:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "previene la rilevazione dei vostri dispositivi tramite l'API del Gamepad" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "Azioni hotkey disponibili" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "Temporaneamente consentire / bloccare tutte le risorse per una scheda corrente" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "Rimuovere i permessi temporanei per una scheda corrente" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "Rimuovere tutte le autorizzazioni temporanee" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "Configurare i tasti di scelta rapida ScriptSafe" + }, + "hotkeysinst": { + "description": "click on Keyboard Shortcuts", + "message": "clicca sui Tasti di scelta rapida" + }, + "listallsettings": { + "description": "List All Settings", + "message": "Mostra Tutte le Impostazioni" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "Ignorato Consenti" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "Le impostazioni più aggiornate sono state scaricate con successo!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "Impostazioni Scaricate!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "Raggruppare Tutte le Impostazioni" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "Rimuovere Possibile Monitoraggio Hash:" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "rimuovere possibile monitoraggio di tokens passati utilizzando hash, dove vi è un attributo e un valore (ad esempio #xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "Nascondere" + }, + "import": { + "description": "Import", + "message": "Importare" + }, + "importexport": { + "description": "Import / Restore Settings", + "message": "Importa / Ripristina Impostazioni" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "Impostazioni importate con successo" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "Impostazioni importate con successo, ad eccezione dei seguenti (valore vuoto o il nome non riconosciuto):" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "Impostazioni importate con successo e sincronizzazione in 10 secondi" + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "Ridurre Tracciatura Della Tastiera (per utenti avanzati):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "rendere i tempi di pressione dei tasti più casuale per aumentare l'anonimato (Nota: aggiunge un ritardo casuale tra le pressioni dei tasti, disabilitare questa impostazione se inaccettabile)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behaviour:", + "message": "Comportamento del Collegamento alla Pagina:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "modifica come tutti i link vengono aperti" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "Permissivo - consentire stesso dominio e sottodomini" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "Modalità di Default" + }, + "newtab": { + "description": "New Tab", + "message": "Nuova Scheda" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "Questa scheda è stata caricata senza risorse esterne" + }, + "notfiltered": { + "description": "Not filtered", + "message": "Non filtrato" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "Questo browser non supporta la protezione WebRTC" + }, + "off": { + "description": "-Off-", + "message": "-Off-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "Solo su Domini Unwhitelisted" + }, + "options": { + "description": "Options", + "message": "Opzioni" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "Modalità Paranoia:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "blocco domini permesso su domini non quotati" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "Incollare nelle impostazioni e riprovare" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "Rispetta Stesso-Dominio:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "preservare gli elementi dello stesso dominio" + }, + "privacy": { + "description": "Privacy Settings", + "message": "Impostazioni Privacy" + }, + "random": { + "description": "Random", + "message": "Casuale" + }, + "rating": { + "description": "Show Rating Button:", + "message": "Mosta il Pulsante di Valutazione:" + }, + "ratingbtn": { + "description": "Rating", + "message": "Valutazione" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "se selezionato, aggiunge il pulsante Valutazione sotto i domini nella scheda popup" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafe è stato recentemente aggiornato / ricaricato.

Sarà necessario o aggiornare questa scheda, creare una nuova scheda, o riavviare il browser affinchè ScriptSafe funzioni correttamente." + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "Bloccare Referrer Clicca-Attraverso:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to On All Domains may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "bloccare le informazioni del referrer quando si clicca sui link di terze parti (nota: abilitarlo su tutti i domini può causare problemi (ad esempio nel visualizzare le miniature in Tweetdeck))" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "Spoof del Referrer:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may break some sites (e.g. logging in)", + "message": "avviso: se abilitato, può bloccare alcuni siti (ad esempio il login)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "Auto-Aggiorna Pagina:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "auto-aggiorna pagina dopo il cambio elenco" + }, + "relaxed": { + "description": "Relaxed", + "message": "Rilassato" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "Revoca Le Autorizzazioni Temporanee Della Pagina" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "Revoca Temporaneamente Tutto" + }, + "same": { + "description": "Same Document", + "message": "Lo Stesso Documento" + }, + "sametab": { + "description": "Same Tab", + "message": "Stessa Scheda" + }, + "save": { + "description": "Save", + "message": "Salvare" + }, + "savetxt": { + "description": "Save as Text File", + "message": "Salva come File di Testo" + }, + "sections": { + "description": "Sections", + "message": "Sezioni" + }, + "settingsall": { + "description": "select all", + "message": "seleziona tutto" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "Copiare e incollare le impostazioni che si desidera importare in ScriptSafe in questa casella quindi fare clic sul pulsante Importa." + }, + "settingssave": { + "description": "Settings saved", + "message": "Impostazioni salvate" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "Impostazioni salvate e sincronizzazione in 10 secondi" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "Visualizza la voce di Menu Contestuale:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafe è disabilitato" + }, + "strict": { + "description": "Strict", + "message": "Rigoroso" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "Rigoroso - consentire solo stesso dominio" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "Per sostenere lo sviluppo, fare clic sul cuore :)" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on Sync Settings FROM Google Account.", + "message": "È stata attivata la sincronizzazione automatica. Al fine di evitare la cancellazione dei dati precedentemente sincronizzati (se presente), si prega di fare clic su impostazioni di sincronizzazione dal tuo account Google." + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "Impostazioni di sincronizzazione DALL'account Google" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "Impostazioni di sincronizzazione ALL'account Google" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "ScriptSafe ha rilevato impostazioni sincronizzate sul tuo account Google!\r\nClicca su 'OK' se si desidera importare le impostazioni dal tuo account Google." + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "La sincronizzazione è stata disattivata per evitare di sovrascrivere i dati già sincronizzati.\r\nSentitevi liberi di andare alla pagina delle opzioni in qualsiasi momento per sincronizzare le impostazioni (fare un backup delle impostazioni, se necessario)." + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "Mostra Notifiche di Importazione-Sincronizzazione:" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": "mostra un popup quando le impostazioni vengono sincronizzate dal vostro account Google" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "Mostra Notifica della Sincronizzazione:" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "mostrare un popup quando le impostazioni vengono sincronizzate nel vostro account Google" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "La vostra attuale versione di Google Chrome non supporta la sincronizzazione delle impostazioni. Si prega di provare ad aggiornare la versione di Chrome e riprovare." + }, + "temp": { + "description": "Temporary", + "message": "Temporaneo" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "Fuso Orario Spoof:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "camuffa o randomizza il vostro fuso orario. NOTA: se attivata, potrebbe interferire con la risposta a messaggi di posta elettronica in Gmail." + }, + "trust": { + "description": "Trust", + "message": "Fiducia" + }, + "trustlow": { + "description": "trust", + "message": "fiducia" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "Applicare anche ai domini autorizzati" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside Show Update Popup.", + "message": "Sei sicuro di voler disattivare eventuali notifiche di aggiornamento futuro come questa dall'apparire?\r\nÈ possibile sempre ri-attivare le notifiche di aggiornamento andando alla pagina Opzioni di ScriptSafe e selezionando la casella accanto a Mostra aggiornamento Popup." + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "Notifiche di aggiornamento disabilitate" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "Mostra Changelog Dell'Aggiornamento:" + }, + "unwanted": { + "description": "Unwanted", + "message": "Non Desiderato" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "mostra la pagina del changelog quando ScriptSafe viene aggiornato" + }, + "url": { + "description": "Domain", + "message": "Dominio" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "Inserisci un dominio o espressione (fare clic su 'Help' per maggiori informazioni)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "User-Agent Spoof:" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "camuffa il vostro user-agent (browser e OS)" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "Inserire un indirizzo da impostare come valore referrer per tutti i siti" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "Rimuove il Monitoraggio di Google Analytics (UTM):" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "rimuove il tracciamento tokens di Google Analytics (UTM)" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "Rimuovere Webbugs:" + }, + "webbugsdesc": { + "description": "remove invisible third-party elements", + "message": "rimuovere gli elementi di terze parti invisibili" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "Blocca Tracciamento WebGL:" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "evitare impronte digitali tramite l'API di WebGL" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "Blocca Enumerazione del Dispositivo:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "previene la rilevazione dei dispositivi hardware tramite l'API WebRTC" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "Protezione WebRTC:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "evitare perdite indirizzo IP" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "Blocca Enumerazione del WebVR:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "previene la rilevazione dei vostri dispositivi tramite l'API del WebVR" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ Blacklist" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ Whitelist" + }, + "blacklist": { + "description": "Blacklist", + "message": "Blacklist" + }, + "whitelist": { + "description": "Whitelist", + "message": "Whitelist" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "Nella Blacklist" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "Nella Whitelist" + }, + "blacklistlow": { + "description": "blacklist", + "message": "blacklist" + }, + "whitelistlow": { + "description": "whitelist", + "message": "whitelist" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "Sposta nella Blacklist" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "Sposta nella Whitelist" + }, + "whitelistblacklist": { + "description": "Whitelist / Blacklist", + "message": "Whitelist / Blacklist" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "XML Richiesta HTTP Manipolazione:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "Controllare Tutte le Richieste" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "Controlla le Richieste di Cross-Domain (consentire con lo stesso dominio)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "controlla richieste HTTP XML" + } +} diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/ja/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/ja/messages.json new file mode 100644 index 0000000..8e9da37 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/ja/messages.json @@ -0,0 +1,858 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "より安全にウェブやサーフィンの制御を取り戻します。" + }, + "alldomains": { + "description": "On All Domains", + "message": "すべてのドメイン上の" + }, + "allow": { + "description": "Allow", + "message": "許可" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "許可されたアイテム" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "ブロックされたアイテム" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "ブロックされたセッションをすべて許可" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "不要なコンテンツをブロックします:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "知られている広告/マルウェアのドメインから不要なコンテンツを削除します。ドメインはMVPSのHOSTShpHOSTS(広告/追跡サーバ)から収集した、ピーター・ロウズHOSTSプロジェクトMalwareDomainList.com" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "不要なコンテンツモード:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "寛容=ホワイトリストに登録されたドメインはブロックされません。厳格=不要なドメインは、ホワイトリストに登録されていてもブロックされます" + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "SNS 拒否/ブロック" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "SNS 拒否/ブロックモード:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "常にホワイトリストに登録しても、SNS ウィジェット/ボタンを削除" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "より包括的なブロッキングについては、Privacy BadgerDisconnectBluruBlock Origin および Fanboy site上のサブスクリプションリストをチェックしてください" + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "ブロックオーディオフィンガープリント:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "AudioContext APIを介してフィンガープリントを防ぎます" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "ブロックバッテリーフィンガープリント:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "バッテリーのAPIを介してフィンガープリントを防ぎます" + }, + "behavior": { + "description": "Behaviour Settings", + "message": "行動の設定" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "ブロック(推奨)" + }, + "block": { + "description": "Block", + "message": "ブロック" + }, + "blocked": { + "description": "Blocked", + "message": "ブロックされました" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "許可したすべてのセッションをブロック" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "ブロックBluetooth列挙:" + }, + "bluetoothdesc": { + "description": "prevent having devices detected via the Bluetooth API", + "message": "Bluetooth APIを介して検出されたデバイスを防ぎます" + }, + "bulkimport": { + "description": "bulk import", + "message": "一括インポート" + }, + "bulkbtn": { + "description": "Import to List", + "message": "リストにインポート" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "一括インポート" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "下のボックスにコピー&ペーストドメイン。各ドメインは、別の行にする必要があります。" + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "キャンバスフィンガープリント保護:" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page.

Please try visiting a website.", + "message": "ScriptSafeは、このページを処理することはできません。

ウェブサイトを訪問してみてください。" + }, + "canvasblank": { + "description": "Blank Readout", + "message": "空白を読み出し" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "ランダム読み出し" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "読み出しを完全にブロック" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "<canvas>要素を介して、フィンガープリントの試みに対する保護" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "ブロックキャンバスフォントのアクセス:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "<canvas>要素を介して列挙されることからシステムフォントを防ぎます。 Googleドキュメントに干渉することがあります。" + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "クラシックオプションモード:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "チェックが入っている場合、毎回オ​​プションをクリックすると、タブのオプションを閉じ" + }, + "clear": { + "description": "Clear", + "message": "消去" + }, + "clearlow": { + "description": "clear", + "message": "消去" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "ブロッククライアントレクタングル:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "要素のレクタングルを計算を通じてフィンガープリントを防ぎます。いくつかのドロップダウンを妨げる可能性があります。" + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "クリップボードの干渉を防ぎます。" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "クリップボードの操作に干渉からページを防ぎます" + }, + "close": { + "description": "Close", + "message": "閉じる" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "不要なクッキーをブロックします:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad / malware domains; below mode applies to this as well", + "message": "知られている広告/マルウェアのドメインからブロッククッキー。モードの下だけでなく、これに適用されます" + }, + "custom": { + "description": "Custom", + "message": "カスタム" + }, + "default": { + "description": "Default", + "message": "デフォルト" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "ローカルIPを保護します" + }, + "deny": { + "description": "Deny", + "message": "拒否" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "ローカルおよびパブリックIPを保護します" + }, + "disable": { + "description": "Disable", + "message": "無効にします" + }, + "disabled": { + "description": "disabled", + "message": "使用禁止" + }, + "disabledcap": { + "description": "Disabled", + "message": "使用禁止" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "同期が無効になっています。\r\nあなたの設定を同期する任意の時点で[オプション]ページに移動してお気軽に(必要に応じて、設定のバックアップを作成)。" + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "無効にして削除します。" + }, + "distrust": { + "description": "Distrust", + "message": "不審" + }, + "distrustlow": { + "description": "distrust", + "message": "不審" + }, + "domain": { + "description": "Same Domain", + "message": "同じドメイン" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "ドメインで並べ替え:" + }, + "domaininfo": { + "description": "Help", + "message": "ヘルプ" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "無効なドメイン/アドレス" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "ドメインまたはアドレスは、いくつかの文字/数字を含める必要があります" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see Block Unwanted Content and/or Antisocial Mode)", + "message": "それが不要なコンテンツの提供者であるとして、ドメインを追加することはできません(ブロック不要なコンテンツおよび/またはSNS 拒否/ブロックモードを参照してください)" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "このページ上のドメインにより、パネルにURLリストをソート" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "ヒント:Ctrl + Fキーを押しリストを検索します" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "このページは表示しません" + }, + "enable": { + "description": "Enable:", + "message": "有効にします:" + }, + "enabled": { + "description": "enabled", + "message": "使用可能" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "ScriptSafeを有効にします" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "同期を有効にします:" + }, + "export": { + "description": "Export", + "message": "エクスポート" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "お使いの設定が正常に同期されています!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "設定が同期!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "フィンガープリント保護" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "フィンガープリント保護(サイトを壊す場合があります)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "あなたがまだGoogleアカウントに設定を同期していない表示されます。\r\nScriptSafeは、Googleアカウントに現在の設定を同期しようとしています。\r\n続行する場合は「OK」をクリックします。\r\nない場合は、クリックして「キャンセル」、そしてお好みの設定で他のデバイス上で、ScriptSafeを更新しますが、このメッセージが表示されたら、[OK]をクリックします。" + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "Googleアカウントに現在の設定を同期してもよろしいですか?\r\n注:これを頻繁に押さないでください。毎分10と毎時千の限界があります。" + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "あなたは、このデバイスにGoogleアカウントから同期設定をインポートしますか?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "ホットキー:" + }, + "generalsettings": { + "description": "General Settings", + "message": "一般設定" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "ブロックゲームパッド列挙:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "ゲームパッドAPIを介して検出されたデバイスを防ぎます" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "利用可能なホットキーアクション" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "一時的に許可する/現在のタブのすべてのリソースをブロック" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "現在のタブのための一時的なアクセス権を削除する" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "すべての一時的な権限を削除する" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "ScriptSafeのホットキーを設定します" + }, + "hotkeysinst": { + "description": "click on Keyboard Shortcuts", + "message": "キーボードショートカットをクリックしてください" + }, + "listallsettings": { + "description": "List All Settings", + "message": "すべての設定を一覧表示" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "許可を無視" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "最新の設定が正常にダウンロードされています!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "設定のダウンロード!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "グループのすべての設定" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "可能なハッシュトラッキングを削除します。" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "属性と値があるハッシュを使用して渡さ可能な追跡トークンを削除する(例えば#xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "隠す" + }, + "import": { + "description": "Import", + "message": "インポート" + }, + "importexport": { + "description": "Import / Restore Settings", + "message": "インポート/復元の設定" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "正常にインポート設定" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "以下の(空の値または認識されない名前)を除いて、正常にインポートセッティング:" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "設定が正常にインポートし、10秒で同期" + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "キーボードフィンガープリントを削減 (上級ユーザー向け):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "匿名性を高めるためにキー入力タイミングがよりランダムにします (注:キーの押下の間のランダムな遅延を追加します。この設定が受け入れられない無効)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behaviour:", + "message": "行動を開くページリンク:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "すべてのリンクが開いているか修正" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "ルース - 同じドメインおよびサブドメインを許可します" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "デフォルトモード" + }, + "newtab": { + "description": "New Tab", + "message": "新しいタブ" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "このタブには外部リソースをロードしていません" + }, + "notfiltered": { + "description": "Not filtered", + "message": "フィルタリングされません" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "このブラウザはWebRTCの保護をサポートしていません。" + }, + "off": { + "description": "-Off-", + "message": "-オフ-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "Unwhitelistedドメインでのみ" + }, + "options": { + "description": "Options", + "message": "オプション" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "パラノイアモード:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "非上場のドメイン上のブロック許可されているドメイン" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "設定に貼り付けて再試行してください" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "同じドメインを尊重します:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "同じドメイン要素を保持します" + }, + "privacy": { + "description": "Privacy Settings", + "message": "プライバシー設定" + }, + "random": { + "description": "Random", + "message": "ランダム" + }, + "rating": { + "description": "Show Rating Button:", + "message": "評価ボタンを表示:" + }, + "ratingbtn": { + "description": "Rating", + "message": "評価" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "チェックが入っている場合、タブのポップアップ内のドメインの下に評価ボタンが追加されます" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafeは最近、再読み込み/更新されました。

あなたは、このタブを更新し、新しいタブを作成したり、ScriptSafeが機能するためには、ブラウザを再起動するのいずれかが必要になります。" + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "クリックスルーリファラーをブロックします:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to On All Domains may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "サードパーティのリンクをクリックするブロックリファラー情報(注:すべてのドメイン上にこれを設定するTweetdeckの中の問題(例えば、サムネイルを引き起こす可能性があります)" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "リファラー偽装:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may break some sites (e.g. logging in)", + "message": "警告: 有効にした場合、いくつかのサイトを破ることができる(例えば、ログイン)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "オートリフレッシュページ:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "リスト変更後のオートリフレッシュページ" + }, + "relaxed": { + "description": "Relaxed", + "message": "寛容" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "一時的な許可の取り消し" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "一時的な許可をすべて取り消し" + }, + "same": { + "description": "Same Document", + "message": "同じドキュメント" + }, + "sametab": { + "description": "Same Tab", + "message": "同じタブ" + }, + "save": { + "description": "Save", + "message": "セーブ" + }, + "savetxt": { + "description": "Save as Text File", + "message": "テキストフ​​ァイルとして保存します" + }, + "sections": { + "description": "Sections", + "message": "セクション" + }, + "settingsall": { + "description": "select all", + "message": "すべて選択" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "あなたはこのボックスにScriptSafeにインポートする設定は、[インポート]ボタンをクリックしてコピーして貼り付けます。" + }, + "settingssave": { + "description": "Settings saved", + "message": "設定が保存されました" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "10秒での設定保存と同期" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "コンテキストメニューを表示:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafeは無効になっています" + }, + "strict": { + "description": "Strict", + "message": "厳格" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "厳格 - 同じドメインのみ許可します" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "開発をサポートするために、ハートアイコンをクリックして :)" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on Sync Settings FROM Google Account.", + "message": "あなたは、自動同期を有効にしています。 (もしあれば)あなたの以前に同期されたデータを消去しないようにするためには、Googleアカウントの同期設定]をクリックしてください。" + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "Googleアカウントから同期設定" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "Googleアカウントに同期設定" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "ScriptSafeはGoogleアカウントに同期する設定を持っていることを検出しました!\r\nGoogleアカウントから設定をインポートしたい場合は、「OK」をクリックします。" + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "同期は既に同期されたデータを上書きを防止するために無効にされています。\r\nあなたの設定を同期する任意の時点で[オプション]ページに移動してお気軽に(必要に応じて、設定のバックアップを作成)。" + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "インポート同期の通知を表示します:" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": "設定は、Googleアカウントから同期されたときにポップアップを表示" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "同期通知を表示:" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "設定はGoogleアカウントに同期するときにポップアップを表示" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "Google Chromeののあなたの現在のバージョンでは、同期の設定をサポートしていません。お使いのChromeのバージョンを更新しようとしてから、もう一度お試しください。" + }, + "temp": { + "description": "Temporary", + "message": "一時的" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "偽装タイムゾーン:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "あなたのタイムゾーンをスプーフィングまたはランダム化します。注:有効にした場合、それは、Gmailのメールに返信を妨げる可能性があります。" + }, + "trust": { + "description": "Trust", + "message": "信頼" + }, + "trustlow": { + "description": "trust", + "message": "信頼" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "同様にホワイトリストに登録されたドメインに適用されます" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside Show Update Popup.", + "message": "あなたが表示されてから、このように任意の将来のアップデート通知を無効にしてもよろしいですか?\r\nあなたはいつもScriptSafeオプション]ページに移動して表示を更新ポップアップの横のボックスを刻むことにより、更新通知を再許可することができます。" + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "更新通知は無効" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "アップデートの変更を表示します:" + }, + "unwanted": { + "description": "Unwanted", + "message": "不要な" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "ScriptSafeが更新されたときに更新履歴ページを表示" + }, + "url": { + "description": "Domain", + "message": "ドメイン" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "ドメインまたは式を入力します(詳細は[ヘルプ]をクリックします)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "ユーザーエージェント偽装:" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "あなたのユーザーエージェント(ブラウザとOS)を偽装" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "すべてのサイトのリファラー値として設定するアドレスを入力してください" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "Googleアナリティクス(UTM)の追跡を削除します。" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "Googleアナリティクス(UTM)の追跡トークンを削除" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "Webbugsを削除します。" + }, + "webbugsdesc": { + "description": "remove invisible third-party elements", + "message": "目に見えない、サードパーティ製の要素を削除" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "ブロックWebGLのフィンガープリント:" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "WebGLのAPIを介してフィンガープリントを防ぎます" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "ブロックデバイスの列挙:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "WebRTC APIを介して検出されたハードウェアデバイスを持つ防ぎます" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "WebRTCの保護:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "IPアドレス漏れを防止" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "ブロックWebVR列挙:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "WebVR APIを介して検出されたデバイスを防ぎます" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ブラックリスト" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ホワイトリスト" + }, + "blacklist": { + "description": "Blacklist", + "message": "ブラックリスト" + }, + "whitelist": { + "description": "Whitelist", + "message": "ホワイトリスト" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "ブラックリストに載って" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "ホワイトリストに登録" + }, + "blacklistlow": { + "description": "blacklist", + "message": "ブラックリスト" + }, + "whitelistlow": { + "description": "whitelist", + "message": "ホワイトリスト" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "ブラックリストに移動" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "ホワイトリストに移動" + }, + "whitelistblacklist": { + "description": "Whitelist / Blacklist", + "message": "ホワイトリスト/ブラックリスト" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "XML HTTPリクエストの処理:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "すべての要求を制御します" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "コントロールクロスドメインリクエスト(同じドメインを許可)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "コントロールのXML HTTP要求" + } +} \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/ko/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/ko/messages.json new file mode 100644 index 0000000..c9f104c --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/ko/messages.json @@ -0,0 +1,858 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "보다 안전하게 웹 서핑을 제어 합니다." + }, + "alldomains": { + "description": "On All Domains", + "message": "모든 도메인에서" + }, + "allow": { + "description": "Allow", + "message": "허용" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "허용 된 항목" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "차단 된 항목" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "차단 된 세션을 모두 허용" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "불필요한 콘텐츠 차단 :" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "원치 않는 광고 / 악성 도메인을 차단합니다; 차단 목록은 MVPS HOSTS, hpHOSTS(광고 / 추적 서버), Peter Lowe's HOSTS Project, MalwareDomainList.com 에서 가져옵니다." + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "불필요한 콘텐츠 모드 :" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "Relaxed = 화이트리스트에 등록 된 도메인은 차단되지 않습니다; Strict = 화이트리스트에 등록 된 도메인이라도 차단됩니다." + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "SNS 차단" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "SNS 차단 모드 :" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "화이트리스트에 등록 된 경우라도 SNS 위젯 / 버튼은 항상 차단" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "보다 자세한 차단 내용은Privacy Badger, Disconnect, Blur, uBlock Origin의 구독목록과 Fanboy site를 확인하세요." + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "오디오 Fingerprinting 차단 :" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "AudioContext API를 통한 fingerprinting을 방지합니다." + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "배터리 Fingerprinting 차단 :" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "Battery API를 통한 fingerprinting을 방지합니다." + }, + "behavior": { + "description": "Behaviour Settings", + "message": "동작 설정" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "차단 (권장)" + }, + "block": { + "description": "Block", + "message": "차단" + }, + "blocked": { + "description": "Blocked", + "message": "차단 됨" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "허용 된 모든 세션을 차단" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "Bluetooth Enumeration 차단 :" + }, + "bluetoothdesc": { + "description": "prevent having devices detected via the Bluetooth API", + "message": "Bluetooth API를 통한 장치 감지를 방지합니다" + }, + "bulkimport": { + "description": "bulk import", + "message": "대량 가져오기" + }, + "bulkbtn": { + "description": "Import to List", + "message": "목록에서 가져오기" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "대량 가져오기" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "아래의 상자에 복사 및 붙여 넣으세요. 각 도메인은 별도의 행으로 분리되어야 합니다." + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Canvas Fingerprint 보호 :" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page.

Please try visiting a website.", + "message": "이 페이지에서는 ScriptSafe가 작동되지 않습니다.​​

웹 사이트를 방문해보세요." + }, + "canvasblank": { + "description": "Blank Readout", + "message": "공백으로 표시" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "랜덤으로 표시" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "완전히 차단" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "<canvas> 요소를 통한 fingerprinting 시도에 대한 보호" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "글꼴 액세스 Canvas 차단:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "<canvas>요소를 통해 열거되는 시스템 글꼴을 방지합니다. Google 문서 도구 사용시 문제가 생길 수 있습니다." + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "클래식 옵션 모드 :" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "체크가 되어있는 경우, 옵션을 클릭할 때마다 아이콘 옵션탭이 닫힙니다." + }, + "clear": { + "description": "Clear", + "message": "삭제" + }, + "clearlow": { + "description": "clear", + "message": "삭제" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "Client Rectangles 차단 :" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "요소의 사각형 계산을 통한 fingerprinting을 방지합니다. 일부 드롭다운 메뉴에 문제가 생길 수 있습니다." + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "클립 보드의 간섭을 방지합니다:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "페이지의 클립 보드 작업 간섭을 방지합니다" + }, + "close": { + "description": "Close", + "message": "닫기" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "불필요한 쿠키 차단 :" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad / malware domains; below mode applies to this as well", + "message": "원치 않는 광고 / 악성 도메인의 쿠키를 차단합니다." + }, + "custom": { + "description": "Custom", + "message": "사용자 정의" + }, + "default": { + "description": "Default", + "message": "기본" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "로컬 IP를 보호합니다" + }, + "deny": { + "description": "Deny", + "message": "거부" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "로컬 및 공용 ​​IP를 보호합니다" + }, + "disable": { + "description": "Disable", + "message": "사용 중지" + }, + "disabled": { + "description": "disabled", + "message": "사용 중지" + }, + "disabledcap": { + "description": "Disabled", + "message": "사용 중지" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "동기화가 사용 중지 되어 있습니다. \r\n설정을 동기화 하려면 언제든 옵션 페이지에서 선택가능합니다. (필요한 경우 설정 백업 가능)" + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "사용 중지하고 삭제:" + }, + "distrust": { + "description": "Distrust", + "message": "불신" + }, + "distrustlow": { + "description": "distrust", + "message": "불신" + }, + "domain": { + "description": "Same Domain", + "message": "동일한 도메인" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "도메인 정렬 :" + }, + "domaininfo": { + "description": "Help", + "message": "도움말" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "잘못된 도메인 / 주소" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "도메인 또는 주소는 일부 문자 / 숫자를 포함해야 합니다." + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see Block Unwanted Content and/or Antisocial Mode)", + "message": "불필요한 컨텐츠 공급자이기 때문에 도메인을 추가 할 수 없습니다. (불필요한 콘텐츠 차단 / SNS 차단 모드 참조)" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "이 페이지에서 도메인을 통한 URL 목록으로 패널 정렬" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "팁 : Ctrl + F를 눌러 목록을 검색합니다." + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "이 페이지는 다시 표시하지 않습니다." + }, + "enable": { + "description": "Enable:", + "message": "사용 :" + }, + "enabled": { + "description": "enabled", + "message": "사용" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "ScriptSafe 사용" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "동기화 사용 :" + }, + "export": { + "description": "Export", + "message": "내보내기" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "설정이 성공적으로 동기화되어 있습니다!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "설정이 동기화 되었습니다!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "Fingerprint 보호" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "Fingerprint 보호 (일부 사이트가 작동되지 않을 수 있습니다)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "아직 Google 계정에 설정을 동기화하지 않았습니다.\r\nScriptSafe는 Google 계정에 현재 설정을 동기화하려 합니다.\r\n계속하려면 'OK'를 취소하려면 'Cancel'을 클릭하세요.\r\n다른 장치에 ScriptSafe의 현재 설정을 적용하려면 이 메시지가 표시되었을때 OK를 클릭하세요." + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "Google 계정에 현재 설정을 동기화하겠습니까?\r\n주의 : 이것을 자주 누르지 마세요; 분당 10번, 시간당 1,000번의 제한이 있습니다." + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "Google 계정에서 동기화 설정을 이 장치에 가져 오시겠습니까?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "단축키 :" + }, + "generalsettings": { + "description": "General Settings", + "message": "일반 설정" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "Gamepad Enumeration 차단 :" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "Gamepad API를 통한 장치 감지를 방지합니다" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "사용 가능한 단축키 액션" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "일시적으로 현재 탭의 모든 리소스를 허용 / 차단" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "현재 탭에 대한 일시적 허용 제거" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "모든 일시적 허용 제거" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "ScriptSafe 단축키 설정" + }, + "hotkeysinst": { + "description": "click on Keyboard Shortcuts", + "message": "키보드 단축키를 클릭하세요" + }, + "listallsettings": { + "description": "List All Settings", + "message": "모든 설정을 나열" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "허용을 무시" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "최근 설정이 성공적으로 다운로드 되고 있습니다!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "설정 다운로드!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "그룹의 모든 설정" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "추적이 가능한 해시 삭제 :" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "해시를 사용하여 전달 한 속성 값 추적 토큰을 제거 (e.g.#xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "숨기기" + }, + "import": { + "description": "Import", + "message": "가져오기" + }, + "importexport": { + "description": "Import / Restore Settings", + "message": "가져오기 / 복원 설정" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "성공적으로 설정을 가져왔습니다." + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "다음 값(비었거나 인식되지 않는 이름)을 제외하고 성공적으로 설정을 가져왔습니다:" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "설정을 성공적으로 가져왔으며 10 초내로 동기화됩니다." + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "키보드 Fingerprinting 방지 (고급 사용자 전용):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "익명성을 높이기 위해 키 입력 타이밍을 랜덤으로 합니다.(주의 : 키를 눌렀을때 임의의 지연을 추가합니다. 받아 들여지지 않는 경우 사용 중지 하세요)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behaviour:", + "message": "페이지 링크를 열 때 동작 :" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "모든 링크를 여는 방법 수정" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "Loose - 같은 도메인 및 하위 도메인을 허용" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "기본 모드" + }, + "newtab": { + "description": "New Tab", + "message": "새 탭" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "이 탭에서는 외부 리소스를 로드하지 않았습니다." + }, + "notfiltered": { + "description": "Not filtered", + "message": "필터링되지 않았습니다" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "이 브라우저는 WebRTC 보호를 지원하지 않습니다." + }, + "off": { + "description": "-Off-", + "message": "-끄기-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "Unwhitelisted 도메인에서만" + }, + "options": { + "description": "Options", + "message": "옵션" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "Paranoia 모드 :" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "목록에 없는 도메인에서는 허용된 도메인들도 차단" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "설정에 붙여넣어 다시 시도하십시오" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "같은 도메인 관계 :" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "같은 도메인 요소를 유지합니다" + }, + "privacy": { + "description": "Privacy Settings", + "message": "개인 정보 설정" + }, + "random": { + "description": "Random", + "message": "랜덤" + }, + "rating": { + "description": "Show Rating Button:", + "message": "평가 버튼을 표시 :" + }, + "ratingbtn": { + "description": "Rating", + "message": "평가" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "체크가 되어있는 경우 아이콘 팝업의 도메인 아래에 평가 버튼이 추가됩니다" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafe가 최근 업데이트 / 리로드 되었습니다.

ScriptSafe가 정상 작동하기 위해서는 탭을 새로고침 하거나 새 탭을 만들거나 브라우저를 다시 시작해야 합니다." + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "클릭을 통한 Referrer 차단 :" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to On All Domains may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "제 3자의 링크를 클릭할때 제공되는 referrer 정보 차단(주의 : 모든 도메인에 이를 설정하면 문제가 발생할 수 있습니다. (e.g. Tweetdeck에서 미리보기 등)" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "가짜 Referrer:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may break some sites (e.g. logging in)", + "message": "경고 : 사용 설정 시 여러 사이트에서 문제가 생길 수 있습니다. (e.g. 로그인 등)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "페이지 자동 새로고침 :" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "목록 변경 후 페이지 자동 새로고침" + }, + "relaxed": { + "description": "Relaxed", + "message": "Relaxed" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "페이지의 일시적 허용을 취소" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "모든 일시적 허용을 취소" + }, + "same": { + "description": "Same Document", + "message": "동일한 문서" + }, + "sametab": { + "description": "Same Tab", + "message": "동일한 탭" + }, + "save": { + "description": "Save", + "message": "저장" + }, + "savetxt": { + "description": "Save as Text File", + "message": "텍스트 파일로 저장합니다." + }, + "sections": { + "description": "Sections", + "message": "섹션" + }, + "settingsall": { + "description": "select all", + "message": "모두 선택" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "ScriptSafe로 가져올 설정을 이 상자에 복사 붙여넣기 한 다음 가져오기 버튼을 클릭합니다." + }, + "settingssave": { + "description": "Settings saved", + "message": "설정이 저장되었습니다." + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "설정이 저장되었으며 10 초내에 동기화 됩니다." + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "컨텍스트 메뉴에 표시:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafe는 사용 중지 되어 있습니다." + }, + "strict": { + "description": "Strict", + "message": "Strict" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "Strict - 같은 도메인만 허용" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "개발을 지원해 주시려면, 하트 아이콘을 클릭해주세요 :)" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on Sync Settings FROM Google Account.", + "message": "현재 자동 동기화를 사용하고 있습니다. 당신의 이전 동기화 된 데이터를 삭제하지 않도록 하려면 Google 계정의 동기화 설정을 클릭하세요." + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "Google 계정에서 동기화 설정 가져오기" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "Google 계정으로 동기화 설정 내보내기" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "Google 계정에서 ScriptSafe 동기화 설정을 발견했습니다!\r\nGoogle 계정에서 설정을 가져 오려면 'OK'를 클릭하세요." + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "동기화는 이미 동기화 된 데이터를 덮어 쓰는 것을 방지하기 위해 사용 중지 되어 있습니다.\r\n동기화로 변경하려면 언제든지 옵션 페이지에서 설정 가능합니다. (필요한 경우 설정 백업 가능)" + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "가져오기 된 동기화 알림 표시 :" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": " 설정이 Google 계정에서 가져오기 된 경우 팝업 알림" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "동기화 알림 표시 :" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "설정이 Google 계정으로 내보내기 된 경우 팝업 알림" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "Google Chrome의 현재 버전이 동기화 설정을 지원하지 않습니다. Chrome 버전을 업데이트하고 다시 시도하십시오." + }, + "temp": { + "description": "Temporary", + "message": "일시적 허용" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "가짜 시간대 :" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "현재 시간대를 속이거나 랜덤화 합니다. 주의 : 사용 설정 시 Gmail 등 이메일 답장에 문제가 생길 수 있습니다." + }, + "trust": { + "description": "Trust", + "message": "신뢰" + }, + "trustlow": { + "description": "trust", + "message": "신뢰" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "화이트리스트에 등록 된 도메인에 적용됩니다." + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside Show Update Popup.", + "message": "이 같은 향후 업데이트 알림을 해제 하시겠습니까?\r\n당신은 언제든지 ScriptSafe 옵션 페이지로 이동하여 업데이트 팝업 상자를 체크해 업데이트 알림을 다시 허용 할 수 있습니다." + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "업데이트 알림이 사용 중지 됨" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "업데이트 변경 내용 표시 :" + }, + "unwanted": { + "description": "Unwanted", + "message": "불필요" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "ScriptSafe가 업데이트 될 때 업데이트 내역 페이지를 표시" + }, + "url": { + "description": "Domain", + "message": "도메인" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "도메인 또는 수식을 입력합니다. (자세한 내용은 '도움말'을 클릭하세요)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "가짜 User-Agent :" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "User-Agent (브라우저와 OS)를 속입니다." + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "모든 사이트의 referrer 값 설정 주소를 입력하세요" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "Google 웹 로그 분석 (UTM) 추적 삭제 :" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "Google 웹 로그 분석 (UTM)의 추적 토큰을 삭제합니다." + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "Webbugs 삭제 :" + }, + "webbugsdesc": { + "description": "remove invisible third-party elements", + "message": "눈에 보이지 않는 제 3자 요소를 제거" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "WebGL Fingerprinting 차단 :" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "WebGL API를 통한 fingerprinting을 방지합니다." + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "Device Enumeration 차단 :" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "WebRTC API를 통한 하드웨어 장치 감지를 방지합니다" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "WebRTC 보호 :" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "IP 주소 누출을 방지" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "WebVR Enumeration 차단 :" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "WebVR API를 통한 장치 감지를 방지합니다" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ 블랙리스트" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ 화이트리스트" + }, + "blacklist": { + "description": "Blacklist", + "message": "블랙리스트" + }, + "whitelist": { + "description": "Whitelist", + "message": "화이트리스트" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "블랙리스트에 등록 됨" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "화이트리스트에 등록 됨" + }, + "blacklistlow": { + "description": "blacklist", + "message": "블랙리스트" + }, + "whitelistlow": { + "description": "whitelist", + "message": "화이트리스트" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "블랙리스트로 이동" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "화이트리스트로 이동" + }, + "whitelistblacklist": { + "description": "Whitelist / Blacklist", + "message": "화이트리스트 / 블랙리스트" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "XML HTTP 요청 처리 :" + }, + "xmlall": { + "description": "Control All Requests", + "message": "모든 요청을 제어" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "Cross-Domain 요청 제어 (동일한 도메인 허용)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "XML HTTP 요청을 제어합니다." + } +} \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/lv/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/lv/messages.json new file mode 100644 index 0000000..7b5aa2f --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/lv/messages.json @@ -0,0 +1,858 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "Atgūt kontroli pār WEB, un sērfot drošāk." + }, + "alldomains": { + "description": "On All Domains", + "message": "Uz Visiem Domēniem" + }, + "allow": { + "description": "Allow", + "message": "Atļaut" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "Atļautie elementi" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "Bloķētie elementi" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "Atļaut visu bloķēto šajā sessijā" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "Nevēlāmā satura bloķēšana:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad/malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad/tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "dzēš kaitīgo/reklāmas zināmo domēnu nevēlamo saturu; domēni savākti no MVPS hosts, hpHOSTS ( ad/sekošanas serveri), Peter Lowe hosts Project, MalwareDomainList.com" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "Nevēlamā satura režīms:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "Atvieglots = baltā saraksta domēni netiks bloķēti; Stingrs = domēni, nevēlamo domēnu sarakstā tiks bloķēti, pat ja neatrodas atļauto sarakstā" + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "Sociālie tīkli" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "Sociālo tīklu režīms:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "vienmēr dzēš sociālo tīklu tēmas/pogas, pat ja atrodas atļauto sarakstā" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "Pilnīgākai bloķēšanai, atslēdziet Privacy Badger, Disconnect, Blur, un/vai uBlock Origin ar visiem abonešanas sarakstiem Fanboy vietnē" + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "Bloķēt Audio izdrukas:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "novērš izdrukas, izmantojot AudioContext API" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "Bloķēt baterijas izdrukas:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "novērš izdrukas, izmantojot Battery API" + }, + "behavior": { + "description": "Behaviour Settings", + "message": "Darbību iestatījumi" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "Bloķēt (ieteicams)" + }, + "block": { + "description": "Block", + "message": "Bloķēt" + }, + "blocked": { + "description": "Blocked", + "message": "bloķēts" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "Bloķēt visu atļauto šajā sessijā" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "Bloķēt Bluetooth sarakstu:" + }, + "bluetoothdesc": { + "description": "prevent having devices detected via the Bluetooth API", + "message": "novērš pieejamo ierīču saraksta iegūšanu, Bluetooth API izmantojot, atrastos" + }, + "bulkimport": { + "description": "bulk import", + "message": "lielapjoma importēšana" + }, + "bulkbtn": { + "description": "Import to List", + "message": "Importēt sarakstā" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "Lielapjoma importēšana" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "Kopējiet un ievietojiet domēnus lodziņā zemāk. Katrs domēns rakstās atsevišķā rindā." + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Aizsardzība pret Canvas izdrukām:" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page.

Please try visiting a website.", + "message": "ScriptSafe neizdodas apstrādāt šo lapu.

Lūdzu, meģiniet apmeklēt mājas lapu." + }, + "canvasblank": { + "description": "Blank Readout", + "message": "Tukša skaita nolasīšana" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "Nejauša skaita nolasīšana" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "Pilnīga Bloka skaita nolasīšana" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "aizsargā pret izdrukas <canvas> elementiem" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "Bloķē Canvas piekļuvi fontiem:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "novērš sistēmas fontu saraksta iegūšanu, izmantojot <canvas> elementus. Var traucēt Google Docs." + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "Klasiskie iestatījumi:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "ja ir iespējots, aizver cilnes iestatījumus ar klikšķi" + }, + "clear": { + "description": "Clear", + "message": "Notīrīt" + }, + "clearlow": { + "description": "clear", + "message": "notīrīt" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "Bloķēt klienta taisnstūri:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "novērš izdrukas, izmantojot taisnstūru elementu aprēķinus. Var radīt traucējumus izlecošo logu sarakstos" + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "Starpliktuves traucējumu novēršana:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "novērš lapu iejaukšanos starpliktuves darbībā" + }, + "close": { + "description": "Close", + "message": "Aizvērt" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "Bloķēt nevēlamās Sīkdatnes:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad/malware domains; below mode applies to this as well", + "message": "bloķē kaitīgo/reklāmas zināmo domēnu sīkdatnes; uz šo attiecas režīmi" + }, + "custom": { + "description": "Custom", + "message": "Lietotāja režīms" + }, + "default": { + "description": "Default", + "message": "Režīms Pēc noklusējuma" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "Lokālā IP aizsardzība" + }, + "deny": { + "description": "Deny", + "message": "Aizliegt" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "Aizsargā lokālās un publiskās IP" + }, + "disable": { + "description": "Disable", + "message": "Atspējot" + }, + "disabled": { + "description": "disabled", + "message": "atspējots" + }, + "disabledcap": { + "description": "Disabled", + "message": "Atspējots" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Sinhronizācija ir atspējota.\r\nNekautrējieties doties jebkurā laikā sinhronizēt iestatījumus (nepieciešamības gadījumā veicot iestatījumu dublējumu)." + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "Atspējot un dzēst:" + }, + "distrust": { + "description": "Distrust", + "message": "Neuzticams" + }, + "distrustlow": { + "description": "distrust", + "message": "neuzticams" + }, + "domain": { + "description": "Same Domain", + "message": "Tas pats domēns" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "Kārtot pēc domēna:" + }, + "domaininfo": { + "description": "Help", + "message": "Palīdzība" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "Nederīgs domēns/Nederīga adrese" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "Domēniem jeb adresēm ir jāsatur dažādus burtus/ciparus" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see Block Unwanted Content and/or Antisocial Mode)", + "message": "Domēnu nevar pievienot, jo tas nes nevēlamu saturu (sk. bloķēt nevēlamu saturu un/vai sociālo tīklu režīms)" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "kārto URL sarakstus pēc domēna šajā lapā un panelī" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "Padoms: nospiediet CTRL+F, lai meklētu sarakstus" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "Nerādīt šo lapu vēlreiz" + }, + "enable": { + "description": "Enable:", + "message": "Ieslēgt:" + }, + "enabled": { + "description": "enabled", + "message": "ieslēgts" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "Ieslēgt ScriptSafe" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "Aktivizēt sinhronizāciju:" + }, + "export": { + "description": "Export", + "message": "Eksports" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "Jūsu iestatījumi tika veiksmīgi sinhronizēti!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "Iestatījumi sinhronizēti!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "Aizsardziba pret izdrukām" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "Aizsardziba pret izdrukām (var salauzt vietnes)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "Škiet, vēl neesat sinhronizējis iestatījumus savā Google kontā.\r\nScriptSafe sinhronizē jūsu pašreizējos iestatījumus Google kontā.\r\nNoklikšķiniet \"OK\", ja vēlaties turpināt.\r\nJa nevēlaties, noklikšķiniet \"Atcelt\", un citā iericē ar vēlamajiem iestatījumiem, atjauninat ScriptSafe un noklikškiniet OK, kad saņemsiet šo ziņojumu." + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "Vai vēlaties sinhronizēt savus pašreizējos iestatījumus savā Google kontā?\r\nPiezīme: lūdzu, neklikšķiniet te bieži; ir ierobežojums 10 reizes minūtē un 1000 reizes stundā." + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "Vai vēlaties importēt no jūsu Google konta sinhronizētos iestatījumus šajā iericē?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "Taustiņi:" + }, + "generalsettings": { + "description": "General Settings", + "message": "Vispārīgie Iestatījumi" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "Bloķēt Gamepad sarakstu:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "novērš pieejamo ierīču saraksta iegūšanu, Gamepad API izmantojot, atrastos" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "Pieejamās karsto taustiņu darbības" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "Īslaicīgi atļaut/bloķēt visus resursus pašreizējā cilnē" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "Dzēst visas pagaidu atļaujas pašreizējā cilnē" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "Dzēst visas pagaidu atļaujas" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "ScriptSafe karsto taustiņu konfigurēšana" + }, + "hotkeysinst": { + "description": "click on Keyboard Shortcuts", + "message": "noklikšķiniet karsto taustiņu" + }, + "listallsettings": { + "description": "List All Settings", + "message": "Visi iestatījumi pēc saraksta" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "Ignorējas Atļautie" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "Jaunākie iestatījumi tika veiksmīgi ielādēti!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "Iestatījumi ielādēti!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "Visi iestatījumi pēc grupas" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "Novērš Hash Sekošanas Iespējāmību:" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "iespējamās sekošanas novēršana, izmantojot hash, kur ir atribūts un vērtība (piemēram, #xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "Slēpt" + }, + "import": { + "description": "Import", + "message": "Imports" + }, + "importexport": { + "description": "Import/Restore Settings", + "message": "Iestatījumu Imports/Atjaunošana" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "Iestatījumi ir veiksmīgi importēti" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "Iestatījumi ir veiksmīgi importēti, izņemot (tukša vērtība vai neatpazīts apzīmējums):" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "Iestatījumi ir veiksmīgi importēti un sinhronizēti 10 sekunžu laikā" + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "Samazināt tastatūraы izdrukas (pieredzējušiem lietotājiem):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "anonimitātes palielināšanai padarīt taustiņu nospiešanas secību nejaušīgāku (pievieno nejaušu taustiņu nospiešanas aizkavi; izslēdziet šo iestatījumu, ja tas nav pieņemams.)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behaviour:", + "message": "Uzvedība saišu atvēršanā:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "mainīt, kā tiks atvērtas saites" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "Brīvs - atļaut to pašu domēnu un apakšdomēnus" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "Režīms Pēc noklusējuma" + }, + "newtab": { + "description": "New Tab", + "message": "Jauna Cilne" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "Ši cilne nelejupielādē nekādus ārējos resursus" + }, + "notfiltered": { + "description": "Not filtered", + "message": "Bez filtriem" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "Šī pārlūkprogramma neatbalsta WebRTC aizsardzību" + }, + "off": { + "description": "-Off-", + "message": "-Atspējots-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "Tikai no neatļauto domēnu saraksta" + }, + "options": { + "description": "Options", + "message": "Iestatījumi" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "Paranoidālais režīms:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "atļauto domēnu bloķēšana nezināmās cilnēs" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "Ievietojiet iestatījumus un mēģiniet vēlreiz" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "Uzticamie domēni:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "attiecīgo domēnu elementu saglabāšana" + }, + "privacy": { + "description": "Privacy Settings", + "message": "Konfidencialitātes iestatījumi" + }, + "random": { + "description": "Random", + "message": "Nejaušs" + }, + "rating": { + "description": "Show Rating Button:", + "message": "Rādit reitinga pogu:" + }, + "ratingbtn": { + "description": "Rating", + "message": "Reitings" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "ja ir iespējots, pievieno domēnu reitinga pogu izlecošā logā un ļauj pārlūkot tās vietnes reputāciju" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafe nesen tika atjaunināts/pārlādēts.

Jums būs nepieciešams vai nu atsvaidzināt šo cilni, izveidot jaunu cilni, vai restartēt parlūkprogrammu." + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "Bloķēt Click-Through Referrer:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to On All Domains may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "klikšķinot uz ārējām saitēm, pārejot, nesaņems informāciju, no kurienes jūs nākat (piezime: uzstādot šo iestatījumu visos domēnos var rasties problēmas (piem. Tweetdeck sīktēli))" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "Nosūtīšanas avota maiņa:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may break some sites (e.g. logging in)", + "message": "brīdinājums: ja iestatījums ir iespējots, var nestrādāt dažas vietnes (piem. ielogoties)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "Lapas automātiskā atjaunināšana:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "Kad jūs izmainīsiet vietnes iestatījumus, lapa automātiski tiks atjaunināta" + }, + "relaxed": { + "description": "Relaxed", + "message": "Atvieglots" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "Īslaicīgi atcelt visas atļaujas" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "Īslaicīgi atcelt visas pagaidu atļaujas" + }, + "same": { + "description": "Same Document", + "message": "Šis dokuments" + }, + "sametab": { + "description": "Same Tab", + "message": "Šī cilne" + }, + "save": { + "description": "Save", + "message": "Saglabāt" + }, + "savetxt": { + "description": "Save as Text File", + "message": "Saglabāt kā Teksta Failu" + }, + "sections": { + "description": "Sections", + "message": "Sadaļas" + }, + "settingsall": { + "description": "select all", + "message": "atlasīt visus" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "Iestatījumus, kurus vēlaties importēt ScriptSafe, nokopējiet un ievietojiet šajā lodziņā, noklikšķiniet Importēt." + }, + "settingssave": { + "description": "Settings saved", + "message": "Iestatījumi saglabāti" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "Iestatījumi ir veiksmīgi saglabāti un sinhronizēti 10 sekunžu laikā" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "Show in Context Menu:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafe ir atspējots" + }, + "strict": { + "description": "Strict", + "message": "Stingrs" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "Stingrs - atļaut tikai šo domēnu" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "ScriptSafe attīstības atbalstīšana, noklikškiniet sirsniņu :)" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on Sync Settings FROM Google Account.", + "message": "Jus esat iespējojis automātisko sinhronizāciju. Lai novērstu savu iepriekš sinhronizēto datu (ja tādi ir) pazušanu, lūdzu, noklikšķiniet uz Sync iestatījumiem no Google konta." + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "Iestatījumi ir sinhronizēti no Google konta" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "Iestatījumi ir sinhronizēti Google kontā" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "ScriptSafe ir konstatējis, ka jums ir sinhronizēti iestatījumi Google kontā!\r\nNoklikšķiniet \"OK\", ja vēlaties importēt iestatījumus no sava Google konta." + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Sinhronizācija ir atspējota, lai novērstu jau sinhronizēto datu parrakstīšanu.\r\nNekautrējieties doties jebkurā laikā sinhronizēt iestatījumus (nepieciešamības gadījumā veicot iestatījumu dublējumu)." + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "Rādīt sinhronizācijas importēšanas paziņojumus:" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": "rāda izlecošā logā, kad iestatījumi tiks sinhronizēti no jūsu Google konta" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "Rādīt sinhronizācijas paziņojumus:" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "rāda izlecošā logā, kad iestatījumi tiks sinhronizēti jūsu Google kontā" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "Jūsu pašreizejā Google Chrome versija neatbalsta sinhronizācijas iestatījumus. Lūdzu, meģiniet atjaunināt Chrome versiju un meģiniet vēlreiz." + }, + "temp": { + "description": "Temporary", + "message": "Pagaidu" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "Mainīt laika joslu:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "Jūsu laika joslas maiņa vai randomizācija. PIEZĪME: ja iestatījums ir iespējots, var traucēt Gmail e-pastiem." + }, + "trust": { + "description": "Trust", + "message": "Uzticēties" + }, + "trustlow": { + "description": "trust", + "message": "uzticēties" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "Pielietot ari baltā saraksta domēniem" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside Show Update Popup.", + "message": "Vai tiešām vēlaties atspējot jebkādus turpmākus atjaunināšanas paziņojumus?\r\nJūs vienmēr varat atkal atļaut nosūtīt paziņojumus par atjauninājumiem, pārejot ScriptSafe iestatījumos un atzimējot Rādīt paziņojumus par atjauninājumiem." + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "Paziņojums par atjauninājumiem ir atspējots" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "Atjaunojoties, rādīt izmaiņas sarakstā:" + }, + "unwanted": { + "description": "Unwanted", + "message": "Nevēlams" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "Kad ScriptSafe tiek atjaunināts, rādīt izmaiņas" + }, + "url": { + "description": "Domain", + "message": "Domēns" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "Ievadiet domēns (noklikšķiniet uz \"Palīdzība\", vairāk info)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "Mainīt User-Agent:" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "HTTP-Pieprasījuma virsraksta maiņa (maina informāciju Interneta pārlūkprogrammā un Operētājsistēmā)" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "Ievadiet adresi, lai uzstādītu referrer vērtību visām vietnēm" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "Dzēst Google Analytics sekošanu (UTM):" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "Google Analytics sekošanas marķieru dzēšana (UTM)" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "Dzēst Webbugs:" + }, + "webbugsdesc": { + "description": "remove invisible third-party elements", + "message": "dzēst iframe-s un citus lietotājam neredzamus elementus, kaitīgo kodu saturošus" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "Bloķēt WebGL izdrukas:" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "novērš izdrukas, izmantojot WebGL API" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "Bloķēt ierīču sarakstu:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "ierīču pieejamības pārbaudes novēršana, WebRTC API izmantojot, atrastos" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "WebRTC Aizsardziba:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "novērš IP adreses noplūdi caur WebRTC" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "Bloķēt WebVR sarakstu:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "novērš pieejamo ierīču saraksta iegūšanu, WebVR API izmantojot, atrastos" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ Melnais Saraksts" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ Baltais Saraksts" + }, + "blacklist": { + "description": "Blacklist", + "message": "Melnais Saraksts" + }, + "whitelist": { + "description": "Whitelist", + "message": "Baltais Saraksts" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "Pievienots melnajā sarakstā" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "Pievienots baltajā sarakstā" + }, + "blacklistlow": { + "description": "blacklist", + "message": "melnais saraksts" + }, + "whitelistlow": { + "description": "whitelist", + "message": "baltais saraksts" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "Pārvietot melnajā sarakstā" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "Pārvietot baltajā sarakstā" + }, + "whitelistblacklist": { + "description": "Whitelist/Blacklist", + "message": "Baltais saraksts/Melnais saraksts" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "XML HTTP pieprasījuma apstrāde:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "Kontrolēt visus pieprasījumus" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "Kontrolēt starpdomēnu pieprasījumus (atļaut to pašu domēnu)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "Kontrolēt XML HTTP pieprasījumus" + } +} diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/nl/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/nl/messages.json new file mode 100644 index 0000000..2fe5543 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/nl/messages.json @@ -0,0 +1,858 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "Herneem controle over het web en surf veiliger." + }, + "alldomains": { + "description": "On All Domains", + "message": "Op alle domeinen" + }, + "allow": { + "description": "Allow", + "message": "Toestaan" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "Toegestane Items" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "Geblokkeerde Items" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "Alle 'Geblokkeerd voor sessie' toestaan" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "Blokkeer ongewenste inhoud:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad/malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad/tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "verwijder ongewenste inhoud van bekende ad/malware domeinen; domeinen verzameld van MVPS HOSTS, hpHOSTS (ad/tracking servers), systemen Project Peter Lowe's, MalwareDomainList.com" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "Ongewenste inhoud mode:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "Relaxed = whitelisted domeinen zullen niet worden geblokkeerd; Strikt = domeinen in de ongewenste domein lijst zullen ook worden geblokkeerd zelfs als whitelisted" + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "Antisociaal" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "Antisociaal mode:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "Verwijder altijd sociale widgets/knoppen, zelfs als whitelisted" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "Voor uitgebreider blokkeren, kijk op Privacy Badger, Disconnect, Blur, en/of uBlock Origin met al de abonnement lijsten op de Fanboy website" + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "Blokkeer audio fingerprinting:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "voorkomt fingerprinting via de AudioContext API" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "Blokkeer Batterij fingerprinting:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "voorkomen fingerprinting via de batterij API" + }, + "behavior": { + "description": "Behaviour Settings", + "message": "Gedrag Instellingen" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "Blokkeren (aanbevolen)" + }, + "block": { + "description": "Block", + "message": "Blokkeren" + }, + "blocked": { + "description": "Blocked", + "message": "Geblokkeerd" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "Alles blokkeren toestaan v​oor sessie" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "Blokkeer Bluetooth Opsomming:" + }, + "bluetoothdesc": { + "description": "prevent having devices detected via the Bluetooth API", + "message": "voorkomen dat apparaten gedetecteerd worden via de Bluetooth API" + }, + "bulkimport": { + "description": "bulk import", + "message": "bulk import" + }, + "bulkbtn": { + "description": "Import to List", + "message": "Importeer naar lijst" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "Bulk import" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "Kopieer en plak domeinen in het vak hieronder. Elk domein moet op een aparte regel." + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Canvas fingerprint bescherming:" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page.

Please try visiting a website.", + "message": "ScriptSafe kan deze pagina niet verwerken.

Probeer een website te bezoeken." + }, + "canvasblank": { + "description": "Blank Readout", + "message": "Blanco uitlezing" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "Willekeurige uitlezing" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "Volledig blokkeren uitlezing" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "beschermt tegen fingerprinting pogingen door middel van <canvas> elementen" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "Blokkeer canvas font toegang:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "voorkom dat systeem fonts worden opgesomd door middel van <canvas> elementen. Kan interfereren met Google Docs." + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "Klassieke opties mode:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "indien aangevinkt, sluit het tabblad opties elke keer dat op een optie wordt geklikt" + }, + "clear": { + "description": "Clear", + "message": "Duidelijk" + }, + "clearlow": { + "description": "clear", + "message": "duidelijk" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "Blokkeer cliënt rechthoeken:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "voorkomen fingerprinting door middel van het berekenen van element rechthoeken. Kan interfereren met enkele dropdowns." + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "Voorkom klembord interferentie:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "voorkom dat pagina's interfereren met klembord acties" + }, + "close": { + "description": "Close", + "message": "Sluiten" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "Blokkeer ongewenste cookies:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad/malware domains; below mode applies to this as well", + "message": "blokkeert cookies van bekende ad/malware domeinen; onderstaande modus geldt ook hiervoor" + }, + "custom": { + "description": "Custom", + "message": "Aangepast" + }, + "default": { + "description": "Default", + "message": "Standaard" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "Bescherm Local IP" + }, + "deny": { + "description": "Deny", + "message": "Weigeren" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "Bescherm lokale en openbare IP's" + }, + "disable": { + "description": "Disable", + "message": "Uitschakelen" + }, + "disabled": { + "description": "disabled", + "message": "uitgeschakeld" + }, + "disabledcap": { + "description": "Disabled", + "message": "Uitgeschakeld" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Synchroniseren is uitgeschakeld.\r\nVoel u vrij om op elk gewenst moment naar de pagina Opties te gaan om uw instellingen te synchroniseren (maak een back-up van uw instellingen indien nodig)." + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "Uitschakelen en verwijderen:" + }, + "distrust": { + "description": "Distrust", + "message": "Wantrouw" + }, + "distrustlow": { + "description": "distrust", + "message": "wantrouw" + }, + "domain": { + "description": "Same Domain", + "message": "Zelfde domein" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "Sorteren op domein:" + }, + "domaininfo": { + "description": "Help", + "message": "Help" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "Ongeldig domein/adres" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "Het domein of adres moet een aantal letters/cijfers bevatten" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see Block Unwanted Content and/or Antisocial Mode)", + "message": "Domein kan niet worden toegevoegd omdat het een leverancier van ongewenste inhoud betreft (zie: Blokkeer ongewenste inhoud en/of Antisociaal mode)" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "sorteert URL lijsten op domeinen op deze pagina en in het paneel" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "Tip: druk op CTRL+F om de lijsten te doorzoeken" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "Deze pagina niet meer weergeven" + }, + "enable": { + "description": "Enable:", + "message": "Inschakelen:" + }, + "enabled": { + "description": "enabled", + "message": "ingeschakeld" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "ScriptSafe inschakelen" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "Schakel Synchronisatie in:" + }, + "export": { + "description": "Export", + "message": "Exporteren" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "Uw instellingen zijn succesvol gesynchroniseerd!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "Instellingen gesynchroniseerd!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "Fingerprint bescherming" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "Fingerprint bescherming (kan sites breken)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "Het lijkt erop dat u de instellingen nog niet heeft gesynchroniseerd met uw Google-account.\r\nScriptSafe staat op het punt de huidige instellingen te synchroniseren met uw Google-account.\r\nKlik op 'OK' als u wilt doorgaan.\r\nZo niet, klikt dan op 'Annuleren', en op het andere apparaat met de gewenste instellingen, update ScriptSafe en klik op 'OK' wanneer deze boodschap wordt getoond." + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "Wilt u uw huidige instellingen synchroniseren met uw Google-account?\r\nLet op: druk hier niet te vaak; Er is een limiet van 10 per minuut en 1000 per uur." + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "Wilt u de gesynchroniseerde instellingen van uw Google-account importeren naar dit apparaat?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "Sneltoetsen:" + }, + "generalsettings": { + "description": "General Settings", + "message": "Algemene Instellingen" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "Blokkeer Gamepad Opsomming:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "voorkomen dat apparaten gedetecteerd worden via de Gamepad API" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "Beschikbare sneltoets acties" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "Tijdelijk toestaan/blokkeren van alle middelen voor een huidig tabblad" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "Verwijder de tijdelijke toestemmingen voor een huidig tabblad" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "Verwijder alle tijdelijke toestemmingen" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "Configureer ScriptSafe sneltoetsen" + }, + "hotkeysinst": { + "description": "click on Keyboard Shortcuts", + "message": "klik op Sneltoetsen" + }, + "listallsettings": { + "description": "List All Settings", + "message": "Toon alle instellingen" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "Genegeerd Toestaan" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "De nieuwste instellingen zijn succesvol gedownload!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "Instellingen Gedownload!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "Alle Instellingen" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "Verwijder mogelijke hash tracking:" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "verwijder alle eventuele tracking tokens doorgegeven met behulp van hash, waar sprake is van een attribuut en de waarde (bijvoorbeeld #xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "Verbergen" + }, + "import": { + "description": "Import", + "message": "Importeren" + }, + "importexport": { + "description": "Import/Restore Settings", + "message": "Import/Instellingen herstellen" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "Instellingen met succes geïmporteerd" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "Instellingen geïmporteerd, met uitzondering van de volgende (lege waarde of niet-herkende naam):" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "Instellingen met succes geïmporteerd en synchroniseren in 10 seconden" + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "Verminder keyboard fingerprinting (voor gevorderde gebruikers):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "maak toetsaanslag timing meer willekeurig om de anonimiteit te verhogen (LET OP: voegt een willekeurige vertraging tussen toetsaanslagen; uitschakelen van deze instelling is onaanvaardbaar)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behaviour:", + "message": "Pagina link openen gedrag:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "wijzigt hoe alle links worden geopend" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "Los - staat ​​hetzelfde domein en sub-domeinen toe" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "Standaard modus" + }, + "newtab": { + "description": "New Tab", + "message": "Nieuw tabblad" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "Dit tabblad heeft geen externe bronnen geladen" + }, + "notfiltered": { + "description": "Not filtered", + "message": "Niet gefilterd" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "Deze browser ondersteunt geen WebRTC bescherming" + }, + "off": { + "description": "-Off-", + "message": "-Uit-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "Alleen op Unwhitelisted Domeinen" + }, + "options": { + "description": "Options", + "message": "Opties" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "Paranoïde mode:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "blokkeren toegestaan ​​domeinen op geheime domeinen" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "Plak in de instellingen en probeer het opnieuw" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "Respecteer het zelfde domein:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "behoud dezelfde domein elementen" + }, + "privacy": { + "description": "Privacy Settings", + "message": "Privacy instellingen" + }, + "random": { + "description": "Random", + "message": "Willekeurig" + }, + "rating": { + "description": "Show Rating Button:", + "message": "Toon beoordeling knop:" + }, + "ratingbtn": { + "description": "Rating", + "message": "Beoordeling" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "indien aangevinkt, wordt een beoordeling knop toegevoegd onder domeinen in tab popup" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafe is onlangs bijgewerkt/herladen.

U moet ofwel deze tab vernieuwen, een nieuwe tab maken, of uw browser herstarten om ScriptSafe te laten werken." + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "Blokkeer Click-Through Verwijzer:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to On All Domains may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "blokkeer referrer informatie bij het klikken op links van derden (let op: deze instellen op 'voor alle domeinen' kan problemen opleveren (bijvoorbeeld miniaturen veroorzaken in Tweetdeck))" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "Verwijzer spoof:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may break some sites (e.g. logging in)", + "message": "waarschuwing: indien geactiveerd, kunnen sommige sites breken (bijvoorbeeld inloggen)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "Auto-Refresh pagina:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "auto-refresh, pagina na lijst verandering" + }, + "relaxed": { + "description": "Relaxed", + "message": "Ontspannen" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "Intrekken pagina tijdelijke toestemmingen" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "Intrekken alle tijdelijke" + }, + "same": { + "description": "Same Document", + "message": "Zelfde document" + }, + "sametab": { + "description": "Same Tab", + "message": "Zelfde tab" + }, + "save": { + "description": "Save", + "message": "Opslaan" + }, + "savetxt": { + "description": "Save as Text File", + "message": "Opslaan als tekstbestand" + }, + "sections": { + "description": "Sections", + "message": "Secties" + }, + "settingsall": { + "description": "select all", + "message": "selecteer alles" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "Kopieer en plak de instellingen die u wilt importeren in ScriptSafe in dit vak en klik op de knop Importeren." + }, + "settingssave": { + "description": "Settings saved", + "message": "Instellingen opgeslagen" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "Instellingen opgeslagen en synchroniseren in 10 seconden" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "Weergeven in Context Menu:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafe is uitgeschakeld" + }, + "strict": { + "description": "Strict", + "message": "Strikt" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "Strikt - staat ​​alleen hetzelfde domein toe" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "Om de ontwikkeling te ondersteunen, klikt u op het hart :)" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on Sync Settings FROM Google Account.", + "message": "U heeft automatische synchronisatie ingeschakeld. Om te voorkomen dat uw eerder gesynchroniseerde gegevens worden gewist (indien van toepassing), klikt u op Synchronisatie-instellingen van Google-account." + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "Synchronisatie-instellingen van Google-account" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "Synchronisatie-instellingen naar Google-account" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "ScriptSafe heeft ontdekt dat u de instellingen heeft gesynchroniseerd op uw Google-account!\r\nKlik op 'OK' als u de instellingen van uw Google-account wilt importeren." + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Synchronisatie is uitgeschakeld om het overschrijven van uw reeds gesynchroniseerde gegevens te voorkomen.\r\nVoel u vrij om op elk gewenst moment naar de pagina Opties te gaan om uw instellingen te synchroniseren (maak een back-up van uw instellingen indien nodig)." + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "Toon Import Sync Mededeling:" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": "toon popup wanneer de instellingen gesynchroniseerd zijn vanuit uw Google-account" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "Toon Sync mededeling:" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "toon popup wanneer de instellingen gesynchroniseerd zijn met uw Google-account" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "Uw huidige versie van Google Chrome ondersteunt geen instellingen synchroniseren. Probeer het bijwerken van uw Chrome-versie en probeer het opnieuw." + }, + "temp": { + "description": "Temporary", + "message": "Tijdelijk" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "Spoof Tijdzone:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "spoof of randomiseer uw tijdzone. NB: indien geactiveerd, kan het interfereren met het beantwoorden van e-mails in Gmail." + }, + "trust": { + "description": "Trust", + "message": "Vertrouw" + }, + "trustlow": { + "description": "trust", + "message": "vertrouw" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "Ook toepassen op de wwhitelisted domeinen" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside Show Update Popup.", + "message": "Weet u zeker dat u toekomstige update meldingen wilt uitschakelen zoals deze worden weergegeven?\r\nU kunt altijd opnieuw updatemeldingen laten doorgeven, door naar de pagina ScriptSafe opties te gaan en het vakje naast 'Toon update popup' aan te vinken." + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "Update meldingen uitgeschakeld" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "Laat Changelog zien bij Update:" + }, + "unwanted": { + "description": "Unwanted", + "message": "Ongewenste" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "toon changelog pagina wanneer ScriptSafe wordt bijgewerkt" + }, + "url": { + "description": "Domain", + "message": "Domein" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "Voer een domein of expressie in (klik op 'Help' voor meer info)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "User-Agent Spoof:" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "spooft uw user-agent (browser en OS)" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "Voer een adres in als uw verwijzer waarde voor alle sites" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "Verwijder Google Analytics (UTM) Tracking:" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "verwijderen van Google Analytics (UTM) tracking tokens" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "Verwijder Webbugs:" + }, + "webbugsdesc": { + "description": "remove invisible third-party elements", + "message": "verwijder onzichtbare elementen van derden" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "Blokkeer WebGL fingerprinting:" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "voorkomt fingerprinting via de WebGL-API" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "Blokkeer apparaat opsomming:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "voorkomen dat hardware-apparaten gedetecteerd worden via de WebRTC API" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "WebRTC bescherming:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "voorkomen IP-adres lekkage" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "Blokkeer WebVR Opsomming:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "voorkomen dat apparaten gedetecteerd worden via de WebVR API" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ Blacklist" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ Whitelist" + }, + "blacklist": { + "description": "Blacklist", + "message": "Blacklist" + }, + "whitelist": { + "description": "Whitelist", + "message": "Whitelist" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "Blacklisted" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "Whitelisted" + }, + "blacklistlow": { + "description": "blacklist", + "message": "blacklist" + }, + "whitelistlow": { + "description": "whitelist", + "message": "whitelist" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "Verplaatsen naar Blacklist" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "Verplaatsen naar Whitelist" + }, + "whitelistblacklist": { + "description": "Whitelist/Blacklist", + "message": "Whitelist/Blacklist" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "XML HTTP Vraag afhandeling:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "Controleer alle aanvragen" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "Controleer Cross-Domain aanvragen (mogelijk met hetzelfde domein)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "controleer XML HTTP aanvragen" + } +} \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/pl/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/pl/messages.json new file mode 100644 index 0000000..f1616fb --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/pl/messages.json @@ -0,0 +1,850 @@ +{ + "alldomains": { + "description": "On All Domains", + "message": "Na Wszystkich Domenach" + }, + "allow": { + "description": "Allow", + "message": "Zezwól" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "Zezwól Wszystkim Zablokowanym Dla Sesji" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "Elementy Dozwolone" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "Blokowanie Niechcianej Treści:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad / malware domains; domains gathered from \u003Ca href=\"http://winhelp2002.mvps.org/hosts.htm\" target=\"_blank\">MVPS HOSTS\u003C/a>, \u003Ca href=\"http://hosts-file.net\" target=\"_blank\">hpHOSTS (ad / tracking servers)\u003C/a>, \u003Ca href=\"http://pgl.yoyo.org/as/policy.php\" target=\"_blank\">Peter Lowe's HOSTS Project\u003C/a>, \u003Ca href=\"http://www.malwaredomainlist.com/\" target=\"_blank\">MalwareDomainList.com\u003C/a>", + "message": "usuń niechciane treści ze znanych domen reklamowych / złośliwych; domeny zebrane z \u003Ca href=\"http://winhelp2002.mvps.org/hosts.htm\" target=\"_blank\">MVPS HOSTS\u003C/a>, \u003Ca href=\"http://hosts-file.net\" target=\"_blank\">hpHOSTS (ad / tracking servers)\u003C/a>, \u003Ca href=\"http://pgl.yoyo.org/as/policy.php\" target=\"_blank\">Peter Lowe's HOSTS Project\u003C/a>, \u003Ca href=\"http://www.malwaredomainlist.com/\" target=\"_blank\">MalwareDomainList.com\u003C/a>" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "Tryb Niechcianych Treści:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "Rozluźniony = domeny z białej listy nie będą blokowane; Ścisły = domeny z listy niechcianych domen zostaną zablokowane nawet, jeśli umieszczono je na białej liście" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "Tryb Antyspołecznościowy:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "zawsze usuwaj widżety/przyciski społecznościowe, nawet jeśli są na białej liście" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out \u003Ca href=\"https://chrome.google.com/webstore/detail/privacy-badger/pkehgijcmpdhfbdbbnkijodmdjhbjlgp\" target=\"_blank\">Privacy Badger\u003C/a>, \u003Ca href=\"https://chrome.google.com/webstore/detail/disconnect/jeoacafpbcihiomhlakheieifhpjdfeo\" target=\"_blank\">Disconnect\u003C/a>, \u003Ca href=\"https://chrome.google.com/webstore/detail/blur/epanfjkfahimkgomnigadpkobaefekcd\" target=\"_blank\">Blur\u003C/a>, and/or \u003Ca href=\"https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm\" target=\"_blank\">uBlock Origin\u003C/a> with all of the subscription lists on the \u003Ca href=\"https://www.fanboy.co.nz/\" target=\"_blank\">Fanboy site\u003C/a>", + "message": "Aby uzyskać bardziej kompleksowe blokowanie, sprawdź \u003Ca href=\"https://chrome.google.com/webstore/detail/privacy-badger/pkehgijcmpdhfbdbbnkijodmdjhbjlgp\" target=\"_blank\">Privacy Badger\u003C/a>, \u003Ca href=\"https://chrome.google.com/webstore/detail/disconnect/jeoacafpbcihiomhlakheieifhpjdfeo\" target=\"_blank\">Disconnect\u003C/a>, \u003Ca href=\"https://chrome.google.com/webstore/detail/blur/epanfjkfahimkgomnigadpkobaefekcd\" target=\"_blank\">Blur\u003C/a>, and/or \u003Ca href=\"https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm\" target=\"_blank\">uBlock Origin\u003C/a> ze wszzystkimi listami subskrypcji na \u003Ca href=\"https://www.fanboy.co.nz/\" target=\"_blank\">Fanboy site\u003C/a>" + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "Antyspołecznościowy" + }, + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "Odzyskaj kontrolę w sieci i surfuj bardziej bezpiecznie." + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "Blokuj Odcisk Palca Audio:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "uniemożliwia pobieranie odcisku palca za pośrednictwem interfejsu AudioContext API" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "Dostępne akcje skrótów klawiaturowych" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "Blokuj Pobranie Odcisku Palca Baterii:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "uniemożliwia pobieranie odcisku palca za pośrednictwem interfejsu Battery API" + }, + "behavior": { + "description": "Behavior Settings", + "message": "Ustawienia Postępowania" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ Czarna Lista" + }, + "blacklist": { + "description": "Blacklist", + "message": "Czarna Lista" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "Na Czarnej Liście" + }, + "blacklistlow": { + "description": "blacklist", + "message": "czarna lista" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "Przenieś do Czarnej Listy" + }, + "block": { + "description": "Block", + "message": "Zablokuj" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "Zablokuj Wszystkie Zezwolone Dla sesji" + }, + "blocked": { + "description": "Blocked", + "message": "Zablokowane" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "Elementy Zablokowane" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "Zablokuj (rekomendowane)" + }, + "bulkbtn": { + "description": "Import to List", + "message": "Importuj do Listy" + }, + "bulkimport": { + "description": "bulk import", + "message": "import zbiorczy" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "Import Zbiorczy" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "Skopiuj i wklej domeny do poniższego pola. Każda domena powinna znajdować się w odrębnej lini" + }, + "cannotprocess": { + "description": "\u003Cstrong>ScriptSafe cannot process this page.\u003C/strong>\u003Cbr />\u003Cbr />Please try visiting a website.", + "message": "\u003Cstrong>ScriptSafe nie może przetworzyć tej strony.\u003C/strong>\u003Cbr />\u003Cbr />Proszę spróbować odwiedzić stronę internetową." + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Ochrona Odcisku Palca Elementów Canvas:" + }, + "canvasblank": { + "description": "Blank Readout", + "message": "Pusty Odczyt" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "Całkowicie Zablokuj Odczyt " + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "uniemożliwia próby pobrania odcisku palca przez elementy <canvas>" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "Zablokuj Dostęp Do Elementów Czcionek Canvas:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "uniemożliwia wyliczenie czcionek systemowych przez elementy <canvas>. Może kolidować z Google Docs." + }, + "canvasrandom": { + "description": "Random Readout", + "message": "Losowy Odczyt" + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "Tryb Opcji Klasyczny:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "jeśli zaznaczone, zamyka opcje kart za każdym razem, gdy opcja zostanie klknięta" + }, + "clear": { + "description": "Clear", + "message": "Wyczyść" + }, + "clearlow": { + "description": "clear", + "message": "wyczyść" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "Zablokuj Client Rectangles:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "uniemożliwia pobranie odcisku palca przez elementy rectangles. Może zakłócać niektóre listy rozwijalne." + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "Zapobieganie Ingerencji W Schowek:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "uniemożliwia stronom ingeręcję w działanie schowka" + }, + "close": { + "description": "Close", + "message": "Zamknij" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "Zablokuj Niechciane Ciasteczka:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad / malware domains; below mode applies to this as well", + "message": "blokuje ciasteczka ze znanych domen reklamowych / złośliwych; tryb poniżej również tego dotyczy" + }, + "custom": { + "description": "Custom", + "message": "Niestandartowy" + }, + "default": { + "description": "Default", + "message": "Domyślnie" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "Chroń Lokalny Adres IP" + }, + "deny": { + "description": "Deny", + "message": "Odmów" + }, + "disable": { + "description": "Disable", + "message": "Wyłącz" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "Chroń Lokalny i Publiczny Adres IP" + }, + "disabled": { + "description": "disabled", + "message": "wyłączone" + }, + "disabledcap": { + "description": "Disabled", + "message": "Wyłączone" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Synchronizacja jest wyłączona.\r\nW dowolnym momencie możesz przejść do strony Opcje, aby zsynchronizować twoje ustawienia (w razie potrzeby wykonaj kopię zapasową ustawień)." + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "Wyłącz i Usuń:" + }, + "distrust": { + "description": "Distrust", + "message": "Nie Ufaj" + }, + "distrustlow": { + "description": "distrust", + "message": "nie ufaj" + }, + "domain": { + "description": "Same Domain", + "message": "Ta Sama Domena" + }, + "domaininfo": { + "description": "Help", + "message": "Pomoc" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "Nieprawidłowa domena/adres" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "Domena lub adres musi zawierać kilka liter/cyfr" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see \"Block Unwanted Content\" and/or \"Antisocial Mode\")", + "message": "Domeny nie można dodać, ponieważ jest dostawcą niechcianej treści (zobacz \"Blokowanie Niechcianej Treści\" i/lub \"Tryb Antyspołecznościowy\")" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "Sortuj według Domeny:" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "sortuje listy adresów URL według domen na tej stronie i w panelu" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "Wskazówka: naciśnij klawisze CTRL+F aby wyszukać listy" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "Nie Pokazuj Tej Strony Ponownie" + }, + "enable": { + "description": "Enable:", + "message": "Włączanie:" + }, + "enabled": { + "description": "enabled", + "message": "włączone" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "Włącz ScriptSafe" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "Włączanie Synchronizacji:" + }, + "export": { + "description": "Export", + "message": "Eksport" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "Twoje ustawienia zostały pomyślnie zsynchronizowane!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "Synchronizacja Ustawień!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "Ochrona Odcisku Palca" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "Ochrona Odcisku Palca (może zakłócić działanie stron)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "Wygląda na to, że Ty jeszcze nie masz zsynchronizowanych ustawień z Twoim kontem Google.\r\nScriptSafe zamierza zsynchronizować Twoje bieżące ustawienia dla Twojego konta Google.\r\nKliknij na 'OK' jeśli zamierzasz kontynuować.\r\nJeśli nie, kliknij 'Zakończ', a na drugim urządzeniu z preferowanymi ustawieniami uaktualnij ScriptSafe i klknij OK, gdy pojawi się ten komunikat." + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "Czy chcesz zsynchronizować bieżące ustawienia z Twoim kontem Google?\r\nUwaga: proszę nie naciskać tego często; Limit wynosi 10 razy na minutę i 1000 na godzinę." + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "Czy chcesz zaimportować zsynchronizowane ustawienia z Twojego konta Google na to urządzenie?" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "Zablokuj wykaz kontrolerów gier:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "uniemożliwia wykrycie urządzeń za pomocą interfejsu Gamepad API" + }, + "generalsettings": { + "description": "General Settings", + "message": "Ustawienia Główne" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "Pogrupuj Wszystkie Ustawienia" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "Usuń Możliwe Śledzenie Znacznika Hash:" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "usuwa możliwe tokeny śledzenia przekazywane przy użyciu znacznika hash, gdzie znajduje się atrybut i wartość (e.g. #xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "Ukryj" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "Skróty Klawiszowe:" + }, + "hotkeysinst": { + "description": "click on \"Keyboard Shortcuts\"", + "message": "kliknij na \"Skróty Klawiaturowe\"" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "Konfiguruj skróty klawiszowe ScriptSafe" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "Usuń tymczasowe uprawnienia dla bieżącej karty" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "Usuń wszystkie tymczasowe uprawnienia" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "Tymczasowo zezwól/zablokuj wszystkie zasoby dla bieżącej karty" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "Ignoruj Zezwolone" + }, + "import": { + "description": "Import", + "message": "Import" + }, + "importexport": { + "description": "Import / Restore Settings", + "message": "Importowanie / Przywracanie Ustawień" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "Najnowsze ustawienia zostały pomyślnie pobrane!" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "Ustawienia zostały zaimportowane z powodzeniem, za wyjątkiem następujących wartości (pusta wartość lub nierozpoznana nazwa): " + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "Ustawienia zostały zaimportowane pomyślnie" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 30 seconds", + "message": "Ustawienia zostały zaimportowane pomyślnie i zsynchronizowane w 30 sekund" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "Ustawienia Zostały Pobrane!" + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (\u003Cstrong>for advanced users\u003C/strong>):", + "message": "Ogranicz Odcisk Palca Klawiatury (\u003Cstrong>dla zaawansowanych użytkowników\u003C/strong>):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (\u003Cstrong>NOTE: adds a random delay between keypresses; disable this setting if unacceptable\u003C/strong>)", + "message": "Ustaw czas naciśnięcia klawiszy bardziej losowy, aby zwiększyć anonimowość (\u003Cstrong>UWAGA: dodaje losowe opóźnienie pomiędzy naciśnięciami klawiszy; wyłącz to ustawienie jeśli nie jest akceptowalne\u003C/strong>)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behavior:", + "message": "Zachowanie Przy Otwarciu Odnośnika Na Stronie:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "zmienia sposób otwierania wszystkich łączy" + }, + "listallsettings": { + "description": "List All Settings", + "message": "Lista Wszystkich Ustawień" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "Luźny - pozwala na tą samą domenę i poddomeny" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "Tryb Domyślny" + }, + "newtab": { + "description": "New Tab", + "message": "Nowa Karta" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "Ta karta została załadowana bez zewnętrznych zasobów" + }, + "notfiltered": { + "description": "Not filtered", + "message": "Nie filtrowane" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "Ta przeglądarka nie wspiera ochrony WebRTC" + }, + "off": { + "description": "-Off-", + "message": "-Wyłączone-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "Tylko na Domenach spoza Białej Listy" + }, + "options": { + "description": "Options", + "message": "Opcje" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "Tryb Paranoi:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "blokuje dozwolone domeny na nieznanych domenach" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "Wklej w ustawienia i spróbuj ponownie" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "Przestrzegaj Tej Samej Domeny:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "zachowuje te same elementy domeny" + }, + "privacy": { + "description": "Privacy Settings", + "message": "Ustawienia Prywatności" + }, + "random": { + "description": "Random", + "message": "Losowo" + }, + "rating": { + "description": "Show Rating Button:", + "message": "Pokaż Przycisk Oceny:" + }, + "ratingbtn": { + "description": "Rating", + "message": "Ocena" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "jeśli zaznaczono, dodaje przycisk oceny przy domenach w okienku ScriptSafe" + }, + "recentlyupdated": { + "description": "\u003Cstrong>ScriptSafe was recently updated/reloaded.\u003C/strong>\u003Cbr />\u003Cbr />You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "\u003Cstrong>ScriptSafe został niedawno zaktualizowany/ponownie załadowany.\u003C/strong>\u003Cbr />\u003Cbr />Musisz odświerzyć kartę, utworzyć nową kartę lub zrestartować przeglądarkę aby ScriptSafe zadziałał." + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "Blokuj adres odsyłający po kliknięciu:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to \"On All Domains\" may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "blokuje informacje poleceń odsyłających po kliknięciu linków firm trzecich (uwaga: ustawienie \"Na Wszystkich Domenach\" może powodować problemy (np. dla miniatur w serwisie Tweetdeck))" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "Fałszowanie polecenia odsyłającego:" + }, + "referrerspoofdesc": { + "description": "\u003Cstrong>warning\u003C/strong>: if enabled, may \"break\" some sites (e.g. logging in)", + "message": "\u003Cstrong>uwaga\u003C/strong>: jeśli jest włączone, może \"ograniczać\" niekture witryny (np. przy logowaniu)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "Autoodświerzanie Strony:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "automatycznie odświerza stronę po zmianie listy" + }, + "relaxed": { + "description": "Relaxed", + "message": "Rozluźniony" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "Unieważnij Stronę Uprawnień Tymczasowych" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "Unieważnij Wszystkie Tymczasowe" + }, + "same": { + "description": "Same Document", + "message": "Ten Sam Dokument" + }, + "sametab": { + "description": "Same Tab", + "message": "Ta Sama Karta" + }, + "save": { + "description": "Save", + "message": "Zapisz" + }, + "savetxt": { + "description": "Save as Text File", + "message": "Zapisz Jako Plik Tekstowy" + }, + "sections": { + "description": "Sections", + "message": "Sekcje" + }, + "settingsall": { + "description": "select all", + "message": "zaznacz wszystko" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "Skopiuj i wklej ustawienia, które chcesz zaimportować do ScriptSafe w tym polu, a następnie kliknij przycisk Import." + }, + "settingssave": { + "description": "Settings saved", + "message": "Ustawienia zostały zapisane" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 30 seconds", + "message": "Ustawienia zostały zapisane i zsynchronizowane w ciągu 30 sekund" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "Pokaż Zakładkę ScriptSafe w Menu Kontekstowym:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafe jest wyłączony" + }, + "strict": { + "description": "Strict", + "message": "Ścisły" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "Ścisły - zezwala tylko na tą samą domenę" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "Aby wesprzeć rozwój wtyczki, kliknij w serce :)" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "ScriptSafe wykrył, że masz ustawienia synchronizowane z Twoim kontem Google!\r\nKliknij na 'OK' jeśli chcesz zaimportować ustawienia z Twojego konta Google." + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on \"Sync Settings FROM Google Account\".", + "message": "Masz włączoną automatyczną synchronizację. Aby zapobiec usunięciu poprzednio zsynchronizowanych danych (jeśli istnieją), proszę kliknąć na \"Synchronizuj Ustawienia Z Konta Google\"." + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Synchronizacja została wyłączona, aby zapobiec nadpisywaniu już zsynchronizowanych danych.\r\nW dowolnej chwili możesz przejść do strony Opcje, aby zsynchronizować ustawienia (w razie potrzeby wykonaj kopię zapasową ustawień)." + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "Synchronizuj Ustawienia DO konta Google" + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "Pokaż Powiadomienie o Importowaniu Synchronizacji:" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced \u003Cstrong>from\u003C/strong> your Google Account", + "message": "Wyświetl okienko, kiedy ustawienia zostaną zsynchronizowane \u003Cstrong>z\u003C/strong> Twojego konta Google" + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "Synchronizuj Ustawienia Z konta Google" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "Pokaż Powiadomienie o Synchronizacji:" + }, + "syncnotifydesc": { + "description": "show popup when settings synced \u003Cstrong>to\u003C/strong> your Google Account", + "message": "wyświetl okienko, kiedy ustawienia zostaną zsynchronizowane \u003Cstrong>do\u003C/strong> Twojego konta Google" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "Twoja bieżąca wersja Google Chrome nie obsługuje synchronizacji ustawień. Spróbuj zaktualizować wersję Chrome i spróbuj ponownie." + }, + "temp": { + "description": "Temporary", + "message": "Tymczasowe" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "Fałszowanie Strefy Czasowej:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "fałszuje lub losowo wybiera strefę czasową. UWAGA: jeśli opcja jest włączona, może kolidować z odpowiadaniem na e-maile w Gmail'u." + }, + "trust": { + "description": "Trust", + "message": "Zaufaj" + }, + "trustlow": { + "description": "trust", + "message": "zaufaj" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "Zastosuj również do białej listy domen" + }, + "unwanted": { + "description": "Unwanted", + "message": "Niepożądane" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside \"Show Update Popup\".", + "message": "Czy napewno chcesz wyłączyć wszelkie przyszłe powiadomienia o aktualizacji, takie jak te, które się pojawiają?\r\nZawsze możesz ponownie zezwolić na powiadomienia o aktualizacjach, przechodząc na stronę Opcje ScriptSafe i zaznaczając pole obok \"Wyświetl okienko aktualizacji\"." + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "Powiadomienia o aktualizacjach wyłączone" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "Pokaż Listę Zmian po Aktualizacji:" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "pokazuje stronę z listą zmian, kiedy ScriptSafe zostanie uaktualniony" + }, + "url": { + "description": "Domain", + "message": "Domena" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "Wpisz domenę lub wyrażenie (kliknij 'Pomoc' aby uzyskać więcej informacji)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "Fałszowanie stringu User-Agent:" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "fałszuje twojego user-agenta (przeglądarkę i system operacyjny)" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "Wpisz adres, który chcesz ustawić jako wartość odsyłającą dla wszystkich witryn" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "Usuwanie Śledzenia Google Analytics (UTM):" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "usuwa tokeny śledzenia Google Analytics (UTM)" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "Usuwanie Błędów Sieciowych:" + }, + "webbugsdesc": { + "description": "remove \"invisible\" third-party elements", + "message": "usuwa \"niewidoczne\" elementy firm trzecich" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "Blokowanie Odcisku Palca WebGL:" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "uniemożliwia pobieranie odcisku palca za pomocą interfejsu WebGL API" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "Ochrona WebRTC:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "zapobiega wyciekom adresów IP" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "Blokowanie Wyliczania Urządzeń:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "uniemożliwia wykrycie urządzeń sprzętowych za pośrednictwem interfejsu WebRTC API" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "Blokowanie wyliczania WebVR:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "uniemożliwia wykrycie urządzeń za pośrednictwem interfejsu WebVR API" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ Biała Lista" + }, + "whitelist": { + "description": "Whitelist", + "message": "Biała Lista" + }, + "whitelistblacklist": { + "description": "Whitelist / Blacklist", + "message": "Biała Lista / Czarna Lista" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "Umieszczone na Białej Liście" + }, + "whitelistlow": { + "description": "whitelist", + "message": "biała lista" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "Przenieś do Białej Listy" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "Obsługa Żądań XML HTTP:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "Kontrola Wszystkich Żądań" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "Kontrola żądań międzydomenowych - Cross-Domain (zezwól dla tej samej domeny)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "Kontrola Żądań XML HTTP" + } +} diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/ro/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/ro/messages.json new file mode 100644 index 0000000..89dbc4b --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/ro/messages.json @@ -0,0 +1,858 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "Redobândiți controlul asupra internetului și navigați mai sigur." + }, + "alldomains": { + "description": "On All Domains", + "message": "Pe toate domeniile" + }, + "allow": { + "description": "Allow", + "message": "Permiteți" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "Elemente permise" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "Elemente bocate" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "Permiteți toate elementele blocate pentru sesiune" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "Blocați conținut nedorit:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "eliminați conținutul nedorit din domenii cunoscute ad / malware; Domenii adunate de la hosts MVPS, hpHOSTS ( servere de anunțuri / urmărire), Peter Lowe's HOSTS Project, MalwareDomainList.com" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "Mod de conținut nedorit:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "Relaxat = domeniile din lista albă nu vor fi blocate; Strict = domeniile din lista de domenii nedorite vor fi blocate chiar dacă sunt aflate pe lista albă" + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "Antisocial" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "Mod antisocial:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "eliminați întotdeauna widget-uri / butoane sociale, chiar dacă sunt pe lista albă" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "Pentru blocare mai cuprinzătoare, consultați Privacy Badger, Deconectare, Blur, și / sau uBlock de origine cu toate listele de abonare pe site-ul Fanboy" + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "Blocați amprentarea audio:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "împiedicați amprentarea prin API AudioContext" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "Blocați amprentarea bateriei:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "împiedicați amprentarea prin API-ul bateriei" + }, + "behavior": { + "description": "Behaviour Settings", + "message": "Setări de comportament" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "Blochează (recomandat)" + }, + "block": { + "description": "Block", + "message": "Blocați" + }, + "blocked": { + "description": "Blocked", + "message": "Blocat" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "Blochează toate elementele permise pentru sesiune" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "Blocați enumerarea Bluetooth:" + }, + "bluetoothdesc": { + "description": "prevent having devices detected via the Bluetooth API", + "message": "preveniți detectarea dispozitivelor prin intermediul Bluetooth API" + }, + "bulkimport": { + "description": "bulk import", + "message": "import în masă" + }, + "bulkbtn": { + "description": "Import to List", + "message": "Import la listă" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "Import în masă" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "Copiați și inserați domenii în caseta de mai jos. Fiecare domeniu ar trebui să fie pe o linie separată." + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Protecție împotriva amprentării Canvas:" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page.

Please try visiting a website.", + "message": "ScriptSafe nu poate procesa această pagină.

Vă rugăm să accesați un site web." + }, + "canvasblank": { + "description": "Blank Readout", + "message": "Citire goală" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "Citire aleatorie" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "Blocați complet citirea" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "protejați împotriva amprentării prin încercări <canvas> elemente" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "Blocați accesul la fonturi Canvas:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "împiedicați fonturile de sistem să fie enumerate prin <canvas> elemente. Poate interfera cu Google Docs." + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "Mod clasic de opțiuni:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "dacă este bifată, se închide fila de opțiuni de fiecare dată când se face click pe o opțiune" + }, + "clear": { + "description": "Clear", + "message": "Ștergeți" + }, + "clearlow": { + "description": "clear", + "message": "ștergeți" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "Blocați dreptunghiuri Client:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "împiedicați amprentarea prin calcularea dreptunghiurilor elementului. Poate interfera cu unele meniuri." + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "Previniți interferența Clipboard:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "împiedicați paginile să interfereze cu acțiunile clipboard" + }, + "close": { + "description": "Close", + "message": "Închideți" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "Blocați cookie-urilor nedorite:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad / malware domains; below mode applies to this as well", + "message": "blocați cookie-uri din domenii cunoscute ad / malware; modul de mai jos se aplică și aici, de asemenea" + }, + "custom": { + "description": "Custom", + "message": "Personalizat" + }, + "default": { + "description": "Default", + "message": "Implicit" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "Protejați IP-ul Local" + }, + "deny": { + "description": "Deny", + "message": "Refuzați" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "Protejați IP-uri Publice și Locale" + }, + "disable": { + "description": "Disable", + "message": "Dezactivați" + }, + "disabled": { + "description": "disabled", + "message": "dezactivat" + }, + "disabledcap": { + "description": "Disabled", + "message": "Dezactivat" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Sincronizarea este dezactivată.\r\nSimțiți-vă liber să mergeți la pagina de opțiuni în orice moment pentru a sincroniza setările (faceți o copie de rezervă a setărilor dacă este necesar)." + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "Dezactivați și eliminați:" + }, + "distrust": { + "description": "Distrust", + "message": "Neîncredere" + }, + "distrustlow": { + "description": "distrust", + "message": "neîncredere" + }, + "domain": { + "description": "Same Domain", + "message": "Același domeniu" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "Sortează după domeniu:" + }, + "domaininfo": { + "description": "Help", + "message": "Ajutor" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "Domeniul / adresa invalid/ă" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "Domeniul sau adresa trebuie să conțină niște litere / cifre" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see Block Unwanted Content and/or Antisocial Mode)", + "message": "Domeniul nu poate fi adăugat pentru că este o sursă de conținut nedorit (a se vedea Blocarea de conținut nedorit și / sau modul Antisocial)" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "sortați lista de adrese URL pe domenii de pe această pagină și în panou" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "Sfat: apăsați CTRL + F pentru a căuta listele" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "Nu afișați din nou această pagină" + }, + "enable": { + "description": "Enable:", + "message": "Permiteți:" + }, + "enabled": { + "description": "enabled", + "message": "activat" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "Activaţi ScriptSafe" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "Activați sincronizarea:" + }, + "export": { + "description": "Export", + "message": "Exportați" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "Setările dvs. au fost sincronizate cu succes!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "Setări sincronizate!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "Protecție contra amprentelor" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "Protecție contra amprentelor (poate strica site-uri)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "Se pare că nu ați sincronizat setările în contul dvs. Google.\r\nScriptSafe este pe cale de a sincroniza setările curente în contul dvs. Google.\r\nFaceți clic pe \"OK\" dacă doriți să continuați.\r\nDacă nu, faceți clic pe \"Cancel\", iar pe celălalt dispozitiv cu setările preferate, actualizați ScriptSafe și faceți clic pe OK atunci când sunteți prezentat cu acest mesaj." + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "Doriți să sincronizați setările curente cu contul dvs. Google?\r\nNotă: vă rugăm să nu apăsați în mod frecvent; există o limită de 10 pe minut și 1000 pe oră." + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "Doriți să importați setările sincronizate din contul dvs. Google la acest dispozitiv?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "Hotkeys:" + }, + "generalsettings": { + "description": "General Settings", + "message": "Setari generale" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "Blocați enumerarea Gamepad:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "preveniți detectarea dispozitivelor prin intermediul Gamepad API" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "Acțiuni hotkey disponibile" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "Blocați/Permiteți temporar toate resursele pentru fila curentă" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "Eliminați permisiunile temporare pentru fila curentă" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "Eliminați toate permisiunile temporare" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "Configurați hotkeys pentru ScriptSafe" + }, + "hotkeysinst": { + "description": "click on Keyboard Shortcuts", + "message": "faceți clic pe Comenzi rapide la tastatură" + }, + "listallsettings": { + "description": "List All Settings", + "message": "Listați toate setările" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "Permiteți ignoratele" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "Ultimele setări au fost descărcate cu succes!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "Setări descărcate!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "Grupați toate setările" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "Eliminați posibila urmărire Hash:" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "eliminați posibilii indicatori de urmărire folosind hash, în cazul în care există un atribut și o valoare (de exemplu, #xtor=RSS-1))" + }, + "hide": { + "description": "Hide", + "message": "Ascundeți" + }, + "import": { + "description": "Import", + "message": "Importați" + }, + "importexport": { + "description": "Import / Restore Settings", + "message": "Import / Restabilire setări" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "Setări importate cu succes" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "Setări importate cu succes, cu excepția următoarelor (valoare goală sau nume nerecunoscut):" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "Setările importate cu succes și sincronizare în 10 de secunde" + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "Reduceți amprentarea tastaturii (pentru utilizatorii avansați):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "face timpul apăsării pe taste mai aleator pentru a crește anonimitatea (NOTĂ: adaugă o întârziere aleatorie între apãsãrilor; dezactivați această setare dacă este inacceptabilă)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behaviour:", + "message": "Comportamentul deschiderii paginilor:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "modificați modul în care sunt deschise toate link-urile" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "Slab - permite același domeniu și subdomeniile" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "Mod implicit" + }, + "newtab": { + "description": "New Tab", + "message": "Filă nouă" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "Această filă nu a încărcat resurse externe" + }, + "notfiltered": { + "description": "Not filtered", + "message": "Nefiltrat" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "Acest browser nu acceptă protecția WebRTC" + }, + "off": { + "description": "-Off-", + "message": "-Închis-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "Doar pe domeniile neaflate pe lista albă" + }, + "options": { + "description": "Options", + "message": "Opțiuni" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "Mod paranoic:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "blocați domeniile permise pe domenii nelistate" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "Inserați în setări și încercați din nou" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "Respectă același domeniu:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "păstrați elementele din același domeniu" + }, + "privacy": { + "description": "Privacy Settings", + "message": "Setări de intimitate" + }, + "random": { + "description": "Random", + "message": "Aleator" + }, + "rating": { + "description": "Show Rating Button:", + "message": "Afișați butonul de evaluare:" + }, + "ratingbtn": { + "description": "Rating", + "message": "Evaluare" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "dacă este bifat, adaugă butonul de evaluare sub domeniile din tab-ul pop-up" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafe a fost actualizat recent/reîncărcat.

Trebuie să actualizați această filă, să creați o filă nouă sau reporniți browser-ul pentru ca ScriptSafe să funcționeze." + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "Blocați Click-Through Referrer:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to On All Domains may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "blocați informații referrer atunci când faceți clic pe link-uri terțe-părți (notă: activarea pe toate domeniile poate provoca probleme (de exemplu, imaginile de la TweetDeck))" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "Falsificați Referrer:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may break some sites (e.g. logging in)", + "message": "avertisment: dacă este activat, poate provoca probleme pentru unele site-uri (de exemplu, conectarea)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "Reîncărcați automat pagina:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "pagina se reîncarcă după modificarea listei" + }, + "relaxed": { + "description": "Relaxed", + "message": "Relaxat" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "Revocați permisiunile temporare pentru pagină" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "Revocați tot ce e temporar" + }, + "same": { + "description": "Same Document", + "message": "Acelaşi document" + }, + "sametab": { + "description": "Same Tab", + "message": "Aceeași filă" + }, + "save": { + "description": "Save", + "message": "Salvați" + }, + "savetxt": { + "description": "Save as Text File", + "message": "Salvați ca fișier text" + }, + "sections": { + "description": "Sections", + "message": "Secțiuni" + }, + "settingsall": { + "description": "select all", + "message": "selectați tot" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "Copiați și inserați setările pe care doriți să le importați la ScriptSafe în această casetă, apoi faceți clic pe butonul Import." + }, + "settingssave": { + "description": "Settings saved", + "message": "Setari salvate" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "Setările au fost salvate și sincronizare în 10 de secunde" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "Show in Context Menu:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafe este dezactivat" + }, + "strict": { + "description": "Strict", + "message": "Strict" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "Strict - permiteți doar același domeniu" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "Pentru a sprijini dezvoltarea, faceți clic pe inimă :)" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on Sync Settings FROM Google Account.", + "message": "Ați activat sincronizarea automată. Pentru a preveni ștergerea datelor sincronizate anterior (dacă este cazul), vă rugăm să faceți clic pe Setări sincronizare din contul Google." + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "Sincronizați setări din contul Google" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "Sincronizați setări către contului Google" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "ScriptSafe a detectat că aveți setările sincronizate pe contul dvs. Google!\r\nFaceți clic pe \"OK\" dacă doriți să importați setările din Contul dvs. Google." + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Sincronizarea a fost dezactivată pentru a preveni suprascrierea datelor deja sincronizate.\r\nSimțiți-vă liber să mergeți la pagina de opțiuni în orice moment pentru a sincroniza setările (faceți o copie de rezervă a setărilor dacă este necesar)." + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "Afișați notificările de import a sincronizării:" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": "afisați pop-up atunci când setările sunt sincronizate de la Contul Google" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "Afișați notificare de sincronizare:" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "afisați pop-up atunci când setările sunt sincronizate la contul dvs. Google" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "Versiunea curentă de Google Chrome nu acceptă sincronizarea de setări. Vă rugăm să actualizați versiunea Chrome și încercați din nou." + }, + "temp": { + "description": "Temporary", + "message": "Temporar" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "Imită fusul orar:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "imitată sau alege aleator fusul orar. NOTĂ: dacă este activat, poate interfera cu răspunderea la e-mailuri în Gmail." + }, + "trust": { + "description": "Trust", + "message": "Încredere" + }, + "trustlow": { + "description": "trust", + "message": "încredere" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "Se aplică la domenii din lista albă de asemenea" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside Show Update Popup.", + "message": "Sunteți sigur că doriți să dezactivați orice notificări de actualizare viitoare precum aceasta?\r\nPuteți oricând să repermiteți notificări de actualizare accesând pagina Opțiuni ScriptSafe și bifând caseta de lângă Afișare actualizare Popup." + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "Notificări de actualizare dezactivate" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "Afișați schimbările pe actualizare:" + }, + "unwanted": { + "description": "Unwanted", + "message": "Nedorit" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "arată pagina de schimbări când ScriptSafe este actualizat" + }, + "url": { + "description": "Domain", + "message": "Domeniu" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "Introduceți un domeniu sau o expresie (faceți clic pe \"Ajutor\" pentru mai multe informatii)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "Falsificați User-Agent:" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "Falsificați User-Agent (browser și OS)" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "Introduceți o adresă pentru a seta ca referrer pentru toate site-urile" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "Eliminați urmărirea Google Analytics (UTM):" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "eliminați indicative de urmărire Google Analytics (UTM)" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "Eliminați Webbugs:" + }, + "webbugsdesc": { + "description": "remove invisible third-party elements", + "message": "eliminați elementele invizibile de la terțe-părți" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "Blocați amprentarea WebGL:" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "împiedicați amprentarea prin WebGL API" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "Blocați enumerarea dispozitivelor:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "preveniți detectarea dispozitivelor prin WebRTC API" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "Protecție WebRTC:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "preveniți divulgarea adresei IP" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "Blocați enumerarea WebVR:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "preveniți detectarea dispozitivelor prin intermediul WebVR API" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ Listă neagră" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ Lista albă" + }, + "blacklist": { + "description": "Blacklist", + "message": "Listă neagră" + }, + "whitelist": { + "description": "Whitelist", + "message": "Listă albă" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "În lista neagră" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "În lista albă" + }, + "blacklistlow": { + "description": "blacklist", + "message": "lista neagră" + }, + "whitelistlow": { + "description": "whitelist", + "message": "listă albă" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "Mutați în lista neagră" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "Mutați în lista albă" + }, + "whitelistblacklist": { + "description": "Whitelist / Blacklist", + "message": "Listă albă / Lista neagră" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "Manipularea cererii XML HTTP:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "Controlați toate solicitările" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "Controlați cererile din domenii încrucișate (permit același domeniu)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "controlați cererile XML HTTP" + } +} diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/ru/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/ru/messages.json new file mode 100644 index 0000000..cdfb622 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/ru/messages.json @@ -0,0 +1,858 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "Возвращает контроль над веб, и серфинг более безопасен." + }, + "alldomains": { + "description": "On All Domains", + "message": "На всех доменах" + }, + "allow": { + "description": "Allow", + "message": "Разрешить" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "Разрешенные элементы" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "Заблокированные элементы" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "Разрешить все блокированное на сессию" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "Блокировка нежелательного контента:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "удаляет нежелательный контент из известных рекламных / вредоносных доменов; домены собраны из MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "Режим нежелательного контента:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "Низкий = Белый список доменов не будет заблокирован; Высокий = домены в списке нежелательных доменов будет заблокирован, даже если находятся в белом списке" + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "Социальные сети" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "Режим социальных сетей:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "всегда удаляет социальные виджеты / кнопки, даже если они в белом списке" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "Для более полной блокировки, отключите Privacy Badger, Disconnect, Blur, и / или uBlock Origin со всеми подписками с <а href=https://www.fanboy.co.nz/ target=_blank>сайта Fanboy" + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "Блокировать отпечатки Audio:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "предотвращает снятие отпечатков с помощью API AudioContext" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "Блокировать отпечатки батареи:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "предотвращает снятие отпечатков с помощью API батареи" + }, + "behavior": { + "description": "Behaviour Settings", + "message": "Настройки поведения" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "Блокировать (рекомендуется)" + }, + "block": { + "description": "Block", + "message": "Блокировать" + }, + "blocked": { + "description": "Blocked", + "message": "блокировано" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "Блокировать все разрешенное на сессию" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "Блокировать список с Bluetooth:" + }, + "bluetoothdesc": { + "description": "prevent having devices detected via the Bluetooth API", + "message": "предотвращает получение списка имеющихся устройств, обнаруженных с помощью Bluetooth API" + }, + "bulkimport": { + "description": "bulk import", + "message": "Массовый импорт" + }, + "bulkbtn": { + "description": "Import to List", + "message": "Импорт в список" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "Массовый импорт" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "Копируйте и вставляйте домены в поле ниже. Каждый домен должен находиться на отдельной строке." + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Защита от отпечатков Canvas:" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page.

Please try visiting a website.", + "message": "ScriptSafe не может обработать эту страницу.

Пожалуйста, попробуйте посетить веб-сайт." + }, + "canvasblank": { + "description": "Blank Readout", + "message": "Пустое считывание" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "Случайное считывание" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "Считывание полностью блока" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "защищает от попыток снятия отпечатков через элементы <canvas>" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "Блокировать доступ к шрифтам через Canvas:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "предотвращает получение списка системных шрифтов через элементы <canvas>. Может помешать Документам Google." + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "Классические настройки:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "если галочка установлена, закрывает Параметры вкладки по клику" + }, + "clear": { + "description": "Clear", + "message": "Очистить" + }, + "clearlow": { + "description": "clear", + "message": "очистить" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "Блокировать клиентские прямоугольники:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "предотвращение снятия отпечатков с помощью вычисления прямоугольных элементов. Может создавать помехи в выпадающих списках." + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "Предотвращение помех буфера обмена:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "предотвращение вмешательства страниц в действия с буфером обмена" + }, + "close": { + "description": "Close", + "message": "Закрыть" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "Блокировка нежелательных Cookies:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad / malware domains; below mode applies to this as well", + "message": "блокировка cookies известных рекламных / вредоносных доменов; для этого также применяются режимы ниже" + }, + "custom": { + "description": "Custom", + "message": "Пользовательский" + }, + "default": { + "description": "Default", + "message": "По умолчанию" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "Защита локального IP" + }, + "deny": { + "description": "Deny", + "message": "Запретить" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "Защищает локальные и публичные IP" + }, + "disable": { + "description": "Disable", + "message": "Отключить" + }, + "disabled": { + "description": "disabled", + "message": "отключено" + }, + "disabledcap": { + "description": "Disabled", + "message": "Отключено" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Синхронизация отключена.\r\nНе стесняйтесь перейти на страницу настроек в любое время для синхронизации настроек (делая резервную копию настроек при необходимости)." + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "Отключить и удалить:" + }, + "distrust": { + "description": "Distrust", + "message": "Недоверенный" + }, + "distrustlow": { + "description": "distrust", + "message": "недоверенный" + }, + "domain": { + "description": "Same Domain", + "message": "Тот же домен" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "Сортировка по домену:" + }, + "domaininfo": { + "description": "Help", + "message": "Справка" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "Неверный домен / адрес" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "Домен или адрес должен содержать некоторые буквы / цифры" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see Block Unwanted Content and/or Antisocial Mode)", + "message": "Домен не может быть добавлен, поскольку он является поставщиком нежелательного контента (см. блокировка нежелательного контента и / или режим социальных сетей)" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "сортирует список URL по доменам на этой странице и в панели" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "Подсказка: нажмите CTRL+F для поиска списков" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "Не показывать эту страницу снова" + }, + "enable": { + "description": "Enable:", + "message": "Включить:" + }, + "enabled": { + "description": "enabled", + "message": "включен" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "Включить ScriptSafe" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "Включить синхронизацию:" + }, + "export": { + "description": "Export", + "message": "Экспорт" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "Ваши настройки были успешно синхронизированы!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "Настройки синхронизированы!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "Защита от отпечатков" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "Защита от отпечатков (может сломать сайты)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "Похоже, вы пока не синхронизировали настройки в свой аккаунт Google.\r\nScriptSafe синхронизирует текущие настройки в ваш аккаунт Google.\r\nНажмите на \"OK\", если вы хотите продолжить.\r\nЕсли нет, то нажмите кнопку \"Отмена\", а на другом устройстве с предпочтительными настройками, обновите ScriptSafe и нажмите на кнопку OK, когда у вас появится это сообщение." + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "вы хотите синхронизировать ваши текущие настройки в аккаунте Google?\r\nПримечание: пожалуйста, не нажимайте это часто; есть предел 10 в минуту и 1000 в час." + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "Вы хотите импортировать синхронизированные настройки из аккаунта Google на это устройство?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "Горячие клавиши:" + }, + "generalsettings": { + "description": "General Settings", + "message": "Общие настройки" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "Блокировать список с Gamepad:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "предотвращает получение списка имеющихся устройств, обнаруженных с помощью геймпада API" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "Доступные действия горячих клавиш" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "Временно разрешить / блокировать все ресурсы на текущей вкладке" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "Удалить временные разрешения на текущей вкладке" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "Удалить все временные разрешения" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "Настройка горячих клавиш ScriptSafe" + }, + "hotkeysinst": { + "description": "click on Keyboard Shortcuts", + "message": "нажмите на горячие клавиши" + }, + "listallsettings": { + "description": "List All Settings", + "message": "Все настройки списком" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "Игнорируется Разрешение" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "Последние настройки были успешно загружены!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "Настройки загружены!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "Все настройки по группам" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "Удалить возможные отслеживание хэшей:" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "удаление возможных маркеров отслеживания с помощью хешей, где есть атрибут и значение (например, #xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "Скрыть" + }, + "import": { + "description": "Import", + "message": "Импорт" + }, + "importexport": { + "description": "Import / Restore Settings", + "message": "Импорт / Восстановление параметров" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "Настройки успешно импортированы" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "Настройки успешно импортированы, за исключением следующих (пустое значение или нераспознанное имя):" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "Настройки успешно импортированы и синхронизированы за 10 секунд" + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "Уменьшить отпечатки клавиатуры (для опытных пользователей):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "создание таймингов нажатий клавиш более случайными для повышения анонимности (добавляет случайную задержку между нажатиями; отключите этот параметр, если неприемлемо)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behaviour:", + "message": "Поведение при открытии ссылок:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "изменение, как будут открываться ссылки" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "Свободный - разрешить тот же домен и поддомены" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "Режим по умолчанию" + }, + "newtab": { + "description": "New Tab", + "message": "Новая вкладка" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "Эта вкладка не загружает никаких внешних ресурсов" + }, + "notfiltered": { + "description": "Not filtered", + "message": "Без фильтров" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "Этот браузер не поддерживает защиту WebRTC" + }, + "off": { + "description": "-Off-", + "message": "-Отключено-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "Только на доменах не из белого списка" + }, + "options": { + "description": "Options", + "message": "Опции" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "Параноидальный режим:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "блокирование разрешенных доменов на неизвестных вкладках" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "Вставьте настройки и повторите попытку" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "Доверенные домены:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "ссохранение элементов с тем же доменом" + }, + "privacy": { + "description": "Privacy Settings", + "message": "Настройки конфиденциальности" + }, + "random": { + "description": "Random", + "message": "Случайный" + }, + "rating": { + "description": "Show Rating Button:", + "message": "Показать кнопку Рейтинг:" + }, + "ratingbtn": { + "description": "Rating", + "message": "Рейтинг" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "добавляет кнопку рейтинга доменов во всплывающем окне и позволяет перейти к просмотру репутации сайта" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafe был недавно обновлен / перезагружен.

Вам нужно будет либо обновить эту вкладку, либо создать новую вкладку, или перезапустить браузер для того, чтобы ScriptSafe заработал." + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "Блокировать Click-Through Referrer:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to On All Domains may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "при кликах на внешних ссылках страницы, на которые вы переходите, не получают информацию о том, откуда вы пришли (примечание: установка этой опции на всех доменах может вызвать проблемы (например, эскизы в Tweetdeck))" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "Изменить источник отсылки:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may break some sites (e.g. logging in)", + "message": "Предупреждение : если включено, может сломать некоторые сайты (например, с залогиниванием)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "Автоматическое обновление страницы:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "страница автоматически обновляется после того, как вы изменили настройки для просматриваемого сайта" + }, + "relaxed": { + "description": "Relaxed", + "message": "Низкий" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "Временно отменить все разрешения" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "Временно отменить все" + }, + "same": { + "description": "Same Document", + "message": "Этот документ" + }, + "sametab": { + "description": "Same Tab", + "message": "Эта вкладка" + }, + "save": { + "description": "Save", + "message": "Сохранить" + }, + "savetxt": { + "description": "Save as Text File", + "message": "Сохранить как текстовый файл" + }, + "sections": { + "description": "Sections", + "message": "Разделы" + }, + "settingsall": { + "description": "select all", + "message": "выбрать все" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "Скопируйте и вставьте настройки, которые вы хотите импортировать в ScriptSafe, в это поле и нажмите на кнопку Импорт." + }, + "settingssave": { + "description": "Settings saved", + "message": "Настройки сохранены" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "Настройки сохранены и синхронизированы за 10 секунд" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "Показать в контекстном меню:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafe отключен" + }, + "strict": { + "description": "Strict", + "message": "Высокий" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "Высокий - разрешить только этот домен" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "Для поддержки развития, нажмите на сердечко :)" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on Sync Settings FROM Google Account.", + "message": "Вы включили автоматическую синхронизацию. Для предотвращения стирания ваших ранее синхронизированных данных (если таковые имеются), пожалуйста, нажмите на Настройки синхронизации, из аккаунта Google." + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "Настройки синхронизированы из аккаунта Google" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "Настройки синхронизированы в аккаунт Google" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "ScriptSafe обнаружил, что у вас есть синхронизированные настройки в вашем аккаунте Google!\r\nНажмите на \"OK\", если вы хотите импортировать настройки из вашего аккаунта Google." + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Синхронизация была отключена, чтобы предотвратить перезапись ваших уже синхронизированных данных.\r\nНе стесняйтесь перейти на страницу Параметры в любое время для синхронизации настроек (сделайте резервную копию настроек при необходимости)." + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "Показывать уведомление импорта синхронизации:" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": "показывает всплывающее окно, когда настройки будут синхронизироваться с вашего аккаунта Google" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "Показывать уведомление синхронизации:" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "показывает всплывающее окно, когда настройки синхронизируются с аккаунтом Google" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "Ваша текущая версия Google Chrome не поддерживает синхронизацию настроек. Пожалуйста, попробуйте обновить версию Chrome и повторите попытку." + }, + "temp": { + "description": "Temporary", + "message": "Временно" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "Изменить часовой пояс:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "подмена или рандомизация вашего часового пояса. ПРИМЕЧАНИЕ: если эта функция включена, она может мешать отвечать на сообщения электронной почты в Gmail." + }, + "trust": { + "description": "Trust", + "message": "Доверять" + }, + "trustlow": { + "description": "trust", + "message": "доверять" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "Применить также к доменам из белого списка" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside Show Update Popup.", + "message": "Вы уверены, что хотите отключить любые будущие уведомления об обновлении, подобные этому?\r\nВы всегда можете повторно разрешить отправку уведомлений об обновлениях, перейдя на страницу настроек ScriptSafe и установив флажок рядом с полем Показать Всплывающие уведомления об обновлениях." + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "Уведомление об обновлениях отключено" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "Показывать список изменений при обновлении:" + }, + "unwanted": { + "description": "Unwanted", + "message": "Нежелательный" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "показывать страницу изменений, когда ScriptSafe обновляется" + }, + "url": { + "description": "Domain", + "message": "Домен" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "Введите домен или выражение (нажмите \"Справка\" для получения дополнительной информации)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "Изменить User-Agent:" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "изменение заголовка HTTP-запросов (подмена информации о браузере и операционной системе)" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "Введите адрес, чтобы установить в качестве значения реферера для всех сайтов" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "Удалить отслеживание Google Analytics (UTM):" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "удаление маркеров отслеживания Google Analytics (UTM)" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "Удалить Webbugs:" + }, + "webbugsdesc": { + "description": "remove invisible third-party elements", + "message": "удаляет невидимые iframe-ы и прочие элементы, которые незаметны пользователю, но могут содержать вредный код" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "Блокировать отпечатки WebGL:" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "ппредотвращение снятия отпечатков с помощью WebGL API" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "Блокировать список устройств:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "предотвращение проверки наличия устройств, обнаруженных с помощью API WebRTC" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "Защита WebRTC:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "предотвращает утечку IP адреса через WebRTC" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "Блокировать список с WebVR:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "предотвращает получение списка имеющихся устройств, обнаруженных с помощью WebVR API" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ Черный список" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ Белый список" + }, + "blacklist": { + "description": "Blacklist", + "message": "Черный список" + }, + "whitelist": { + "description": "Whitelist", + "message": "Белый список" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "В черный список добавлен" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "В белый список добавлен" + }, + "blacklistlow": { + "description": "blacklist", + "message": "черный список" + }, + "whitelistlow": { + "description": "whitelist", + "message": "белый список" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "Переместить в черный список" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "Переместить в белый список" + }, + "whitelistblacklist": { + "description": "Whitelist / Blacklist", + "message": "Белый список / Черный список" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "Обработка запросов XML HTTP:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "Контроль всех запросов" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "Контроль междоменных запросов (разрешать тот же домен)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "контроль запросов XML HTTP" + } +} diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/sv/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/sv/messages.json new file mode 100644 index 0000000..72c7d4b --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/sv/messages.json @@ -0,0 +1,858 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "Återta kontrollen över internet och surfa säkrare." + }, + "alldomains": { + "description": "On All Domains", + "message": "På Alla Domäner" + }, + "allow": { + "description": "Allow", + "message": "Tillåt" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "Tillåtna Objekt" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "Spärrade Objekt" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "Tillåt Alla Blockerade För Sessionen" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "Blockera Oönskat Innehåll:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "ta bort oönskat innehåll från kända ad / malware domäner; domäner som samlats in från MVP HOSTS hpHOSTS ( ad / tracking servrar), Peter Lowe värdar Project MalwareDomainList.com" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "Oönskat Innehåll-läge:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "Enkel = godkända domäner inte kommer att blockeras; Strikt = domäner i listan oönskade domänen kommer att blockeras, även om de är vitlistade" + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "Antisocial" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "Antisocial-läge:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "Ta alltid bort sociala knappar, även om de är vitlistade" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "För mer omfattande blockering, se Privacy Badger Disconnect Blur och / eller uBlock Origin med alla blockeringslistor på Fanboy webbplats" + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "Blockera Audio Fingeravtryck:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "förhindra fingeravtryck via AudioContext API" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "Blockera Batteri Fingeravtryck:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "förhindra fingeravtryck via batteri API" + }, + "behavior": { + "description": "Behaviour Settings", + "message": "Beteendeinställningar" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "Blockera (rekommenderas)" + }, + "block": { + "description": "Block", + "message": "Blockera" + }, + "blocked": { + "description": "Blocked", + "message": "Blockerad" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "Blockera Alla Tillåtna För Sessionen" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "Blockera Bluetoothidentifikation:" + }, + "bluetoothdesc": { + "description": "prevent having devices detected via the Bluetooth API", + "message": "förhindra att ha anordningar som upptäckts via Bluetooth API" + }, + "bulkimport": { + "description": "bulk import", + "message": "bulk import" + }, + "bulkbtn": { + "description": "Import to List", + "message": "Importera till Listan" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "Bulk Import" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "Kopiera och klistra in domäner i rutan nedan. Varje domän ska vara på en separat rad." + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Canvas Fingeravtryckskydd:" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page.

Please try visiting a website.", + "message": "ScriptSafe kan inte bearbeta denna sida.

Försök att besöka en webbplats." + }, + "canvasblank": { + "description": "Blank Readout", + "message": "Blank Avläsning" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "Slumpmässig Avläsning" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "Blockera All Avläsning" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "skydda mot fingeravtrycksavläsning igenom <canvas> element" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "Blockera Canvastypsnitt:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "förhindra systemtypsnitt från att identifieras genom <canvas> element. Kan störa Googledokument." + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "Klassisk Funktion:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "om markerad, stängs fliken alternativ varje gång ett alternativ klickas" + }, + "clear": { + "description": "Clear", + "message": "Rensa" + }, + "clearlow": { + "description": "clear", + "message": "rensa" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "Blockera Klientrektanglar:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "förhindra fingeravtryck genom beräkning av elementrektanglar. Kan störa vissa menyer." + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "Förhindra Urklippsstörning:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "förhindra sidor från att störa urklippsåtgärder" + }, + "close": { + "description": "Close", + "message": "Stäng" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "Blockera Oönskade Cookies:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad / malware domains; below mode applies to this as well", + "message": "blockerar cookies från kända ad-/malwaredomäner; undre läge gäller detta också" + }, + "custom": { + "description": "Custom", + "message": "Anpassat" + }, + "default": { + "description": "Default", + "message": "Standard" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "Skydda Lokal IP" + }, + "deny": { + "description": "Deny", + "message": "Neka" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "Skydda Lokala och Offentliga IP-adresser" + }, + "disable": { + "description": "Disable", + "message": "Inaktivera" + }, + "disabled": { + "description": "disabled", + "message": "inaktiverad" + }, + "disabledcap": { + "description": "Disabled", + "message": "Inaktiverade" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Synkroniseringen är inaktiverad.\r\nGå till sidan Alternativ när som helst för att synkronisera inställningarna (gör en säkerhetskopia av inställningarna om det behövs)." + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "Inaktivera och Ta Bort:" + }, + "distrust": { + "description": "Distrust", + "message": "Avvisa" + }, + "distrustlow": { + "description": "distrust", + "message": "avvisa" + }, + "domain": { + "description": "Same Domain", + "message": "Samma Domän" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "Sortera efter Domän:" + }, + "domaininfo": { + "description": "Help", + "message": "Hjälp" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "Ogiltig domän/adress" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "Domänen eller adressen måste innehålla vissa bokstäver/siffror" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see Block Unwanted Content and/or Antisocial Mode)", + "message": "Domän kan inte läggas till eftersom det är en leverantör av oönskat innehåll (se Blockera oönskat innehåll och/eller Antisocial Mode)" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "sorterar URL-listor av domäner på denna sida och i panelen" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "Tips: Tryck på Ctrl + F för att söka i listorna" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "Visa Inte Den Här Sidan Igen" + }, + "enable": { + "description": "Enable:", + "message": "Aktivera:" + }, + "enabled": { + "description": "enabled", + "message": "aktiverad" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "Aktivera ScriptSafe" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "Aktivera Synkronisering:" + }, + "export": { + "description": "Export", + "message": "Exportera" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "Inställningarna har synkroniserats!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "Inställningarna Synkroniserade!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "Fingeravtrycksskydd" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "Fingeravtrycksskydd (kan inaktivera sidor)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "Det verkar som om du inte har synkroniserat inställningarna till ditt Googlekonto ännu.\r\nScriptSafe är på väg att synkronisera de aktuella inställningarna till ditt Googlekonto.\r\nKlicka på \"OK\" om du vill fortsätta.\r\nOm inte, klicka på \"Avbryt\", uppdatera ScriptSafe med dina egna inställningar på den andra enheten och klicka på OK när det här meddelandet visas." + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "Vill du synkronisera de aktuella inställningarna till ditt Googlekonto?\r\nOBS: Välj inte detta för ofta; Det finns en gräns på 10 per minut och 1000 per timme." + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "Vill du importera synkroniserade inställningar från ditt Google-konto till den här enheten?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "Snabbval:" + }, + "generalsettings": { + "description": "General Settings", + "message": "Allmänna Inställningar" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "Blockera Gamepadidentifikation:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "förhindra att ha anordningar som upptäckts via Gamepad API" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "Tillgängliga snabbvalsåtgärder" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "Tillfälligt tillåt/blockera alla resurser för en aktuell flik" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "Ta bort tillfälliga tillstånd för en aktuell flik" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "Ta bort alla tillfälliga tillstånd" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "Konfigurera ScriptSafe snabbval" + }, + "hotkeysinst": { + "description": "click on Keyboard Shortcuts", + "message": "klicka på snabbval" + }, + "listallsettings": { + "description": "List All Settings", + "message": "Lista Alla Inställningar" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "Ignoreras Tillåt" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "De senaste inställningarna har hämtats!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "Inställningar Nedladdade!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "Gruppera Alla Inställningar" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "Ta Bort Möjlig Hashspårning:" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "avlägsna eventuella spårningstecken skickade med hash, där det finns ett attribut och värde (t ex #xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "Dölj" + }, + "import": { + "description": "Import", + "message": "Importera" + }, + "importexport": { + "description": "Import / Restore Settings", + "message": "Importera / Återställ Inställningar" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "Inställningar har importerats" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "Inställningar har importerats, utom följande (tomt värde eller okända namn):" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "Inställningar har importerats och synkroniseras om 10 sekunder" + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "Reducera tangentbordsfingeravtryck (för avancerade användare):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "gör knapptryckningsintervall mer slumpmässiga för att öka anonymitet (OBS: lägger till en slumpmässig fördröjning mellan tangenttryckningar, inaktivera den här inställningen om oacceptabla)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behaviour:", + "message": "Sidlänköppningsbeteende:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "modifierar hur alla länkar öppnas" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "Lätt - tillåt samma domän och underdomäner" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "Standardläge" + }, + "newtab": { + "description": "New Tab", + "message": "Ny flik" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "Den här fliken har inte laddat några externa resurser" + }, + "notfiltered": { + "description": "Not filtered", + "message": "Filtreras inte" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "Denna webbläsare stöder inte WebRTC-skydd" + }, + "off": { + "description": "-Off-", + "message": "-Av-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "Endast på svartlistade domäner" + }, + "options": { + "description": "Options", + "message": "Alternativ" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "Paranoidläge:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "blockera tillåtna domäner på olistade domäner" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "Klistra in inställningar och försök igen" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "Respektera med Samma Domän:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "bevara samma-domän-element" + }, + "privacy": { + "description": "Privacy Settings", + "message": "Sekretessinställningar" + }, + "random": { + "description": "Random", + "message": "Slumpmässig" + }, + "rating": { + "description": "Show Rating Button:", + "message": "Visa Betygsknapp:" + }, + "ratingbtn": { + "description": "Rating", + "message": "Betyg" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "om ikryssad, tillägger betygsknappen under domäner i fliken popup" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafe uppdaterades/laddades om nyss.

Du måste antingen uppdatera den här fliken, skapa en ny flik eller starta om webbläsaren för att ScriptSafe ska fungera." + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "Blockera Klickänvisning:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to On All Domains may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "blockerar hänvisningsinformation när du klickar på tredjepartlänkar (OBS: att sätta detta till På Alla Domäner kan orsaka problem (exempelvis miniatyrer i Tweetdeck))" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "Hänvisningsadress-kamouflage:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may break some sites (e.g. logging in)", + "message": "varning: om aktiverad, kan inaktivera vissa platser (t.ex. logga in)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "Uppdatera Automatiskt:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "Uppdatera sidan automatiskt efter liständring" + }, + "relaxed": { + "description": "Relaxed", + "message": "Enkel" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "Återkalla Temporära Tillstånd" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "Återkalla Alla Temporära" + }, + "same": { + "description": "Same Document", + "message": "Samma Dokument" + }, + "sametab": { + "description": "Same Tab", + "message": "Samma Flik" + }, + "save": { + "description": "Save", + "message": "Spara" + }, + "savetxt": { + "description": "Save as Text File", + "message": "Spara som Textfil" + }, + "sections": { + "description": "Sections", + "message": "Sektioner" + }, + "settingsall": { + "description": "select all", + "message": "välj alla" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "Kopiera och klistra in de inställningar du vill importera till ScriptSafe i denna ruta och klicka sedan på knappen Importera." + }, + "settingssave": { + "description": "Settings saved", + "message": "Inställningar Sparade" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "Inställningar har sparats och synkroniseras om 10 sekunder" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "Visa i Snabbmenyn:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafe är inaktiverad" + }, + "strict": { + "description": "Strict", + "message": "Strikt" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "Strikt - tillåter endast samma domän" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "Stöd programutvecklingen genom att klicka på hjärtat :)" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on Sync Settings FROM Google Account.", + "message": "Du har aktiverat automatisk synkronisering. För att förhindra radering av dina tidigare synkroniserade data (i förekommande fall), klicka på inställningar för synkronisering FRÅN Googlekonto." + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "Synkronisera inställningar FRÅN Googlekonto" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "Synkronisera inställningar TILL Googlekonto" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "ScriptSafe har upptäckt att du har inställningar synkroniserade på ditt Googlekonto!\r\nKlicka på \"OK\" om du vill importera inställningarna från ditt Googlekonto." + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "Synkronisering har inaktiverats för att förhindra överskrivning dina redan synkroniserade data.\r\nKänn dig fri att gå till sidan Alternativ när som helst för att synkronisera inställningarna (gör en säkerhetskopia av inställningarna om det behövs)." + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "Visa Importera Synk-information:" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": "visa popup när inställningar synkroniseras från ditt Googlekonto" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "Visa Synk-information:" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "visa popup när inställningar synkroniseras till ditt Googlekonto" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "Din nuvarande version av Google Chrome stöder inte inställningar synkronisering. Uppdatera din version av Chrome och försök igen." + }, + "temp": { + "description": "Temporary", + "message": "Temporär" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "Kamouflera Tidszon:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "kamouflera eller slumpa din tidszon. OBS: om aktiverad, kan det störa svar på e-postmeddelanden i Gmail." + }, + "trust": { + "description": "Trust", + "message": "Lita på" + }, + "trustlow": { + "description": "trust", + "message": "lita på" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "Applicera på godkända domäner också" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside Show Update Popup.", + "message": "Är du säker på att du vill inaktivera eventuella framtida uppdateringsmeddelanden som det här från att visas?\r\nDu kan alltid tillåta uppdateringsnotifieringar igen genom att gå till ScriptSafe sidan Alternativ och markera rutan bredvid Visa Uppdateringsmeddelande." + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "Uppdateringsmeddelanden inaktiverade" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "Visa Ändringslogg på Uppdatering:" + }, + "unwanted": { + "description": "Unwanted", + "message": "Oönskade" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "visa ändringsinformation när ScriptSafe uppdateras" + }, + "url": { + "description": "Domain", + "message": "Domän" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "Ange en domän eller uttryck (klicka på \"Hjälp\" för mer information)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "Webläsarkamouflage:" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "kamouflerar din webläsare och ditt operativsystem" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "Ange en adress för att ställa in som ditt hänvisningsvärde för alla webbplatser" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "Ta bort Google Analytics (UTM)-Spårning:" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "ta bort Google Analytics (UTM)-spårningtokens" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "Ta Bort Webbuggar:" + }, + "webbugsdesc": { + "description": "remove invisible third-party elements", + "message": "ta bort osynliga tredjepartselement" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "Blockera WebGL-Fingeravtryck:" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "förhindra fingeravtryck via WebGL API" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "Blockera enhetsidentifikation:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "förhindra att ha hårdvara upptäcks via WebRTC API" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "WebRTC-Skydd:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "förhindra IP-adressläckage" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "Blockera WebVRidentifikation:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "förhindra att ha anordningar som upptäckts via WebVR API" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ Svartlista" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ Vitlista" + }, + "blacklist": { + "description": "Blacklist", + "message": "Svartlista" + }, + "whitelist": { + "description": "Whitelist", + "message": "Vitlista" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "Svartlistad" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "Vitlistad" + }, + "blacklistlow": { + "description": "blacklist", + "message": "svartlista" + }, + "whitelistlow": { + "description": "whitelist", + "message": "vitlista" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "Flytta till Svartlista" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "Flytta till Vitlista" + }, + "whitelistblacklist": { + "description": "Whitelist / Blacklist", + "message": "Whitelist / Svartlista" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "XML HTTP Begäranshantering:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "Styr alla Begäran" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "Kontrollera Kors-Domänbegäran (tillåta Samma Domän)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "kontroll XML HTTP-begäranden" + } +} diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/zh_CN/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/zh_CN/messages.json new file mode 100644 index 0000000..e953b71 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/zh_CN/messages.json @@ -0,0 +1,859 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "让您的上网更安全" + }, + "alldomains": { + "description": "On All Domains", + "message": "对所有域名有效" + }, + "allow": { + "description": "Allow", + "message": "允许" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "已允许项目" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "已阻挡项目" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "在此会话允许所有已阻挡的项目" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "反广告及反跟踪列表:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "从以下HOSTS聚集网站导入反广告及反跟踪列表MVPS HOSTS hpHOSTS (ad / tracking servers) Peter Lowe's HOSTS Project, MalwareDomainList.com" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "拦截模式:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "一般模式=允许白名单里的域名;严格模式=阻挡白名单里的域名" + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "清净模式" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "清净模式:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "移除白名单里的社交网站小部件" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "想得到更好的效果,可使用以下插件Privacy Badger, Disconnect, Blur, uBlock Origin 并订阅Fanboy的列表" + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "不允许通过音频API接口收集用户信息:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "禁止调用AudioContext API" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "不允许通过电池API接口收集用户信息:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "禁止调用Battery API" + }, + "behavior": { + "description": "Behaviour Settings", + "message": "行为设置" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "不允许加载脚本(推荐)" + }, + "block": { + "description": "Block", + "message": "阻挡" + }, + "blocked": { + "description": "Blocked", + "message": "已阻挡" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "在此会话阻挡所有已允许的项目" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "不允许Bluetooth设备:" + }, + "bluetoothdesc": { + "description": "prevent having hardware devices detected via the Bluetooth API", + "message": "禁止调用Bluetooth API以检测硬件设备" + }, + "bulkimport": { + "description": "bulk import", + "message": "批量导入" + }, + "bulkbtn": { + "description": "Import to List", + "message": "导入到列表" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "批量导入" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "复制和粘贴域名到下面的框中,一行只允许输入一个域名。" + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Canvas元素识别保护:" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page.

Please try visiting a website.", + "message": "本页面不启用ScriptSafe。

请访问别的网站。" + }, + "canvasblank": { + "description": "Blank Readout", + "message": "清空Canvas内容" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "清空Canvas内容并使用随机尺寸" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "不提供内容及尺寸" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "不允许通过Canvas元素收集用户信息" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "不允许通过Canvas元素读取用户字体:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "不允许通过Canvas元素列举用户系统字体,可能与Google Docs有冲突。" + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "传统模式:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "如启用,弹出式选项卡会在每次点击后关闭;如不启用,则需手动关闭弹出式选项卡" + }, + "clear": { + "description": "Clear", + "message": "清除" + }, + "clearlow": { + "description": "clear", + "message": "清除" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "不允许通过Rectangles元素收集用户信息:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "防止通过点算Rectangles元素进行识别,可能会与某些下拉菜单有冲突。" + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "破解网页禁止复制粘贴的脚本:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "" + }, + "close": { + "description": "Close", + "message": "关闭" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "拦截跟踪用户的Cookies:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad / malware domains; below mode applies to this as well", + "message": "从聚集网站导入反广告及反跟踪列表;下面几个选项也是从同样的聚集网站取得列表" + }, + "custom": { + "description": "Custom", + "message": "自定义" + }, + "default": { + "description": "Default", + "message": "默认" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "保护本地IP" + }, + "deny": { + "description": "Deny", + "message": "拒绝" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "保护本地和公网IP" + }, + "disable": { + "description": "Disable", + "message": "禁用" + }, + "disabled": { + "description": "disabled", + "message": "未启用" + }, + "disabledcap": { + "description": "Disabled", + "message": "未启用" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "已禁用同步功能。\r\n可到选项页面启用并同步你的设置(为以防万一,可提前备份您的设置)。" + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "禁用并删除:" + }, + "distrust": { + "description": "Distrust", + "message": "不信任" + }, + "distrustlow": { + "description": "distrust", + "message": "不信任" + }, + "domain": { + "description": "Same Domain", + "message": "同一个域名" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "按域名排序:" + }, + "domaininfo": { + "description": "Help", + "message": "帮助" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "无效的域名或地址" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "该域名或地址必须包含字母或数字" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see Block Unwanted Content and/or Antisocial Mode)", + "message": "不能添加此域名,因为它与“阻止不需要的内容”和“清净模式”有冲突" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "将网址列表按域名排序" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "提示:按Ctrl + F搜索列表" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "不要再显示此页" + }, + "enable": { + "description": "Enable:", + "message": "启用:" + }, + "enabled": { + "description": "enabled", + "message": "已启用" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "启用S​​criptSafe" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "启用同步:" + }, + "export": { + "description": "Export", + "message": "导出" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "您的设置已成功同步!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "设置已同步!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "用户识别与跟踪" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "防止用户信息被收集和识别(可能会影响您浏览的网站的部分功能)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "您的设置还没有同步到Google帐户。\r\nScriptSafe正准备把当前设置同步到您的谷歌帐户。\r\n如果同意,请点击“确认”。\r\n如果不同意,请单击“取消”。您也可以在其它设备上通过此对话框同步您的设置。" + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "需要同步当前设置到您的谷歌帐户吗?\r\n注意:请不要频繁地同步(每分钟限10次和每小时限1000次)。" + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "需要从您的谷歌帐户导入设置到此设备吗?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "热键:" + }, + "generalsettings": { + "description": "General Settings", + "message": "一般设置" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "不允许列举连接到系统的手柄:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "禁止调用手柄API" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "已设置的热键" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "暂时允许/阻挡当前标签页的所有脚本" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "取消当前标签页的临时权限" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "取消所有临时权限" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "设置ScriptSafe热键" + }, + "hotkeysinst": { + "description": "click on Keyboard Shortcuts", + "message": "点击键盘快捷键" + }, + "listallsettings": { + "description": "List All Settings", + "message": "列出所有设置" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "忽略已允许的" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "已成功下载最新的设置!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "设置已下载!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "设置分类显示" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "移除Tracking Tokens的哈希值:" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "Tracking Tokens的属性和值可用于跟踪用户(例如#xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "隐藏" + }, + "import": { + "description": "Import", + "message": "导入" + }, + "importexport": { + "description": "Import / Restore Settings", + "message": "导入/导出设置" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "成功导入设置" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "成功导入设置,以下除外(空值或无法识别的名称):" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "成功导入设置并将在10秒内同步" + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "减少通过键盘识别用户的可能性(高级用户):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "可以使按键时间显得更随机以提高匿名性(注:在每次按键之间插入随机延迟;如对效果不满意,请禁用此设置)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behaviour:", + "message": "设置新链接的打开方式:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "强制新链接在同一标签页或新标签页打开" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "一般模式 - 对相同的域名和子域名都有效" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "默认模式" + }, + "newtab": { + "description": "New Tab", + "message": "新标签" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "此标签没有加载外部资源" + }, + "notfiltered": { + "description": "Not filtered", + "message": "内容未经过滤的" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "该浏览器不支持WebRTC技术保护" + }, + "off": { + "description": "-Off-", + "message": "-未设置-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "仅对黑名单域名有效" + }, + "options": { + "description": "Options", + "message": "选项" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "超严格模式:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "会影响白名单的域名" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "把设置粘贴后再试一次" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "保留相同域名的网页元素:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "" + }, + "privacy": { + "description": "Privacy Settings", + "message": "隐私设置" + }, + "random": { + "description": "Random", + "message": "随机" + }, + "rating": { + "description": "Show Rating Button:", + "message": "显示WOT评分按钮:" + }, + "ratingbtn": { + "description": "Rating", + "message": "评分" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "勾选后可以通过点选网址前的放大镜图标对该网址进行WOT评分" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.

You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafe最近曾被更新或被重新加载。

您可以通过刷新当前标签页、创建一个新标签页,或重新启动浏览器来重新激活ScriptSafe。" + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "不允许Click-Through Referrer:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to On All Domains may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "点击第三方链接时禁止发送Referrer信息。注意:当设置为“对所有域名有效”时可能会出现问题(如TweetDeck的缩略图)" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "Referrer伪装:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may break some sites (e.g. logging in)", + "message": "警告:启用后可能影响所浏览的网站部分功能(如登录)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "自动刷新页面:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "黑白名单更改后自动刷新页面" + }, + "relaxed": { + "description": "Relaxed", + "message": "一般模式" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "撤销当前页的临时权限" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "撤消所有临时权限" + }, + "same": { + "description": "Same Document", + "message": "同一页面" + }, + "sametab": { + "description": "Same Tab", + "message": "同一标签页" + }, + "save": { + "description": "Save", + "message": "保存" + }, + "savetxt": { + "description": "Save as Text File", + "message": "另存为文本文件" + }, + "sections": { + "description": "Sections", + "message": "设置分类" + }, + "settingsall": { + "description": "select all", + "message": "全选" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "可以把之前导出的ScriptSafe设置粘贴到此处,然后点击导入按钮即可。" + }, + "settingssave": { + "description": "Settings saved", + "message": "设置已保存" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "设置已保存并将在10秒内同步" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "在右键菜单显示:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "已禁用ScriptSafe" + }, + "strict": { + "description": "Strict", + "message": "严格模式" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "严格模式 - 仅对相同域名的网页元素有效" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "点击心形图案进行捐助 :)" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on Sync Settings FROM Google Account.", + "message": "已启用自动同步。为了防止之前同步的数据被删除,请点击从谷歌帐户导入。" + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "从谷歌帐户导入" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "导出到谷歌帐户" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "如需从您的谷歌帐户导入之前同步的设置,请点击“OK”。" + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "为了防止已同步的数据被覆盖,已禁用同步功能。\r\n可到选项页面启用并同步你的设置(为以防万一,可提前备份您的设置)。" + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "显示“网络同步导入”的消息通知:" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": "您的谷歌帐户同步设置时弹出消息通知" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "显示“网络同步导出”的消息通知:" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "把设置同步您的谷歌帐户时弹出消息通知" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "您当前的Chrome浏览器版本不支持设置同步。请尝试更新您的Chrome浏览器版本,然后再试一次。" + }, + "temp": { + "description": "Temporary", + "message": "临时允许" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "时区伪装:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "可以伪装成您希望的时区或随机设定一个时区。注意:启用该选项后可能会与Gmail回复邮件出现冲突。" + }, + "trust": { + "description": "Trust", + "message": "信任" + }, + "trustlow": { + "description": "trust", + "message": "信任" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "也适用于已允许的域名" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside Show Update Popup.", + "message": "确定要禁用更新消息通知?\r\n禁用后可在ScriptSafe选项页重新启用更新通知。" + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "禁用更新消息通知" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "显示更新日志:" + }, + "unwanted": { + "description": "Unwanted", + "message": "广告/跟踪" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "成功更新ScriptSafe后显示更新日志" + }, + "url": { + "description": "Domain", + "message": "域名" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "在此输入域名或表达式(更多资讯请点击“帮助”)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": " User-Agent伪装" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "用于伪装浏览器和操作系统的User-Agent" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "把所有网站的Referrer值都设置为这个网址" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "移除谷歌分析(UTM)的追踪:" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "移除谷歌分析(UTM)的Tracking Tokens" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "移除Webbugs:" + }, + "webbugsdesc": { + "description": "remove invisible third-party elements", + "message": "移除隐藏的第三方元素" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "不允许通过WebGL收集用户信息:" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "禁止调用WebGL API" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "不允许列举设备:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "禁止调用WebRTC API以检测硬件设备" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "WebRTC泄漏保护:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "防止IP地址泄漏" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "不允许WebVR设备:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "禁止调用WebVR API以检测硬件设备" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "添加到黑名单" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "添加到白名单" + }, + "blacklist": { + "description": "Blacklist", + "message": "黑名单" + }, + "whitelist": { + "description": "Whitelist", + "message": "白名单" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "被阻挡名单" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "被允许名单" + }, + "blacklistlow": { + "description": "blacklist", + "message": "黑名单" + }, + "whitelistlow": { + "description": "whitelist", + "message": "白名单" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "移入黑名单" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "移入白名单" + }, + "whitelistblacklist": { + "description": "Whitelist / Blacklist", + "message": "白名单/黑名单" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "XML HTTP请求处理:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "控制所有请求" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "控制跨域名请求(允许同域名)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "控制XML HTTP请求" + } +} + diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/zh_TW/messages.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/zh_TW/messages.json new file mode 100644 index 0000000..dcce715 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_locales/zh_TW/messages.json @@ -0,0 +1,858 @@ +{ + "appdescription": { + "description": "Regain control of the web and surf more securely.", + "message": "重掌網頁的控制權和更安全地瀏覽。" + }, + "alldomains": { + "description": "On All Domains", + "message": "在所有網域" + }, + "allow": { + "description": "Allow", + "message": "准許" + }, + "alloweditems": { + "description": "Allowed Items", + "message": "已准許的項目" + }, + "blockeditems": { + "description": "Blocked Items", + "message": "已封鎖的項目" + }, + "allowallblocked": { + "description": "Allow All Blocked For Session", + "message": "為此段准許所有項目" + }, + "annoyances": { + "description": "Block Unwanted Content:", + "message": "封鎖不需要的內容:" + }, + "annoyancesdesc": { + "description": "remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com", + "message": "從以下類別移除不需要的內容:已知的廣告或有毒的網域、從MVPS HOSTS及hpHOSTS(廣告或追蹤伺服器)收集的網域、Peter Lowe的HOSTS習作及MalwareDomainList.com" + }, + "annoyancesmode": { + "description": "Unwanted Content Mode:", + "message": "不需要的內容模式:" + }, + "annoyancesmodedesc": { + "description": "Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted", + "message": "寬鬆 = 列入允許名單的網域不會被封鎖; 嚴格 = 列入「不需要」名單的網域,就算列入允許名單也會被封鎖" + }, + "antisocialpopup": { + "description": "Antisocial", + "message": "反社交網絡" + }, + "antisocial": { + "description": "Antisocial Mode:", + "message": "反社交網絡模式:" + }, + "antisocialdesc": { + "description": "always remove social widgets/buttons, even if whitelisted", + "message": "永遠移除社交小工具或按鈕,即使已經列入允許名單" + }, + "antisocialdesc2": { + "description": "For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site", + "message": "如要更全面地封鎖,請尋找Privacy Badger, Disconnect, Blur, and/or uBlock Origin (所有在 Fanboy 網站的訂閱名單)" + }, + "audioblock": { + "description": "Block Audio Fingerprinting:", + "message": "阻擋音頻指紋識別:" + }, + "audioblockdesc": { + "description": "prevent fingerprinting via the AudioContext API", + "message": "防止經由AudioContext API的指紋識別" + }, + "battery": { + "description": "Block Battery Fingerprinting:", + "message": "阻擋電池指紋識別:" + }, + "batterydesc": { + "description": "prevent fingerprinting via the Battery API", + "message": "防止經由電池API的指紋識別" + }, + "behavior": { + "description": "Behaviour Settings", + "message": "行為設定" + }, + "blockrec": { + "description": "Block (recommended)", + "message": "封鎖(推薦)" + }, + "block": { + "description": "Block", + "message": "封鎖" + }, + "blocked": { + "description": "Blocked", + "message": "已封鎖" + }, + "blockallallowed": { + "description": "Block All Allowed For Session", + "message": "封鎖所有於此段允許的項目" + }, + "bluetooth": { + "description": "Block Bluetooth Enumeration:", + "message": "封鎖Bluetooth手柄計算:" + }, + "bluetoothdesc": { + "description": "prevent having devices detected via the Bluetooth API", + "message": "防止通過Bluetooth API檢測到裝置" + }, + "bulkimport": { + "description": "bulk import", + "message": "大量匯入" + }, + "bulkbtn": { + "description": "Import to List", + "message": "匯入到列表" + }, + "bulkimportcap": { + "description": "Bulk Import", + "message": "大量匯入" + }, + "bulkimportcapdesc": { + "description": "Copy and paste domains into the box below. Each domain should be on a separate line.", + "message": "複製和貼上網域到下面的空格。每個網域應該是獨立的一行。" + }, + "canvas": { + "description": "Canvas Fingerprint Protection:", + "message": "Canvas 的指紋保護:" + }, + "cannotprocess": { + "description": "ScriptSafe cannot process this page. Please try visiting a website.", + "message": "ScriptSafe無法處理此頁面。請嘗試訪問一個網站。" + }, + "canvasblank": { + "description": "Blank Readout", + "message": "空白讀出" + }, + "canvasrandom": { + "description": "Random Readout", + "message": "隨機讀出" + }, + "canvasblock": { + "description": "Completely Block Readout", + "message": "完全阻擋讀出" + }, + "canvasdesc": { + "description": "protect against fingerprinting attempts through <canvas> elements", + "message": "防止嘗試於<canvas>元素套取指紋" + }, + "canvasfont": { + "description": "Block Canvas Font Access:", + "message": "阻擋存取Canvas 字體:" + }, + "canvasfontdesc": { + "description": "prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.", + "message": "防止系統字體從<canvas>元素被列舉。可能干擾Google Document。" + }, + "classicoptions": { + "description": "Classic Options Mode:", + "message": "經典選項模式:" + }, + "classicoptionsdesc": { + "description": "if ticked, closes tab options everytime an option is clicked", + "message": "如果勾選,每次選項被點擊關便閉標籤選項" + }, + "clear": { + "description": "Clear", + "message": "清除" + }, + "clearlow": { + "description": "clear", + "message": "清除" + }, + "clientrects": { + "description": "Block Client Rectangles:", + "message": "封鎖瀏覽器用戶端的矩形座標:" + }, + "clientrectsdesc": { + "description": "prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.", + "message": "防止通過計算元素的矩形座標來套取指紋。可能干擾某些下拉表列。" + }, + "clipboard": { + "description": "Prevent Clipboard Interference:", + "message": "防止剪貼板干擾:" + }, + "clipboarddesc": { + "description": "prevent pages from interfering with clipboard actions", + "message": "防止網頁干擾剪貼板的動作" + }, + "close": { + "description": "Close", + "message": "關閉" + }, + "cookies": { + "description": "Block Unwanted Cookies:", + "message": "封鎖不需要的Cookies:" + }, + "cookiesdesc": { + "description": "blocks cookies from known ad / malware domains; below mode applies to this as well", + "message": "從已知廣告/惡意軟件的網域封鎖Cookies;以下模式均適用" + }, + "custom": { + "description": "Custom", + "message": "自訂" + }, + "default": { + "description": "Default", + "message": "預設" + }, + "default_public_interface_only": { + "description": "Protect Local IP", + "message": "保護本機IP" + }, + "deny": { + "description": "Deny", + "message": "拒絕" + }, + "disable_non_proxied_udp": { + "description": "Protect Local and Public IPs", + "message": "保護本機和公網IP" + }, + "disable": { + "description": "Disable", + "message": "停用" + }, + "disabled": { + "description": "disabled", + "message": "已停用" + }, + "disabledcap": { + "description": "Disabled", + "message": "已停用" + }, + "disabledsync": { + "description": "Syncing is disabled.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "同步已停用。\r\n隨意進入選項頁面,隨時將您的設定同步(如有必要,請備份您的設定)。" + }, + "disableremove": { + "description": "Disable and Remove:", + "message": "停用並移除:" + }, + "distrust": { + "description": "Distrust", + "message": "不信任" + }, + "distrustlow": { + "description": "distrust", + "message": "不信任" + }, + "domain": { + "description": "Same Domain", + "message": "同一網域" + }, + "domainsort": { + "description": "Sort by Domain:", + "message": "以網域排序:" + }, + "domaininfo": { + "description": "Help", + "message": "幫助" + }, + "domaininvalid": { + "description": "Invalid domain/address", + "message": "無效網域或網址" + }, + "domaininvalid2": { + "description": "The domain or address must contain some letters/numbers", + "message": "該網域或網址一定要包含一些字母或數字" + }, + "domaininvalid3": { + "description": "Domain cannot be added as it is a provider of unwanted content (see Block Unwanted Content and/or Antisocial Mode)", + "message": "不能添加網域名因為這並非需要的內容供應者(請參閱封鎖不需要的內容和/或反社交網絡模式)" + }, + "domainsortdesc": { + "description": "sorts URL lists by domains on this page and in the panel", + "message": "從此頁面網域及控制版面排列URL列表" + }, + "domaintip": { + "description": "Tip: press CTRL+F to search the lists", + "message": "提示:按Ctrl + F來搜索列表" + }, + "dontshowpage": { + "description": "Don't Show This Page Again", + "message": "不要再顯示此頁" + }, + "enable": { + "description": "Enable:", + "message": "啟用:" + }, + "enabled": { + "description": "enabled", + "message": "已啟用" + }, + "enabless": { + "description": "Enable ScriptSafe", + "message": "啟用ScriptSafe" + }, + "enablesyncing": { + "description": "Enable Syncing:", + "message": "啟用同步:" + }, + "export": { + "description": "Export", + "message": "匯出" + }, + "exportsuccess": { + "description": "Your settings have been successfully synced!", + "message": "您的設定已成功同步!" + }, + "exportsuccesstitle": { + "description": "Settings Synced!", + "message": "設定已同步!" + }, + "fingerprint": { + "description": "Fingerprint Protection", + "message": "指紋保護" + }, + "fingerprintdesc": { + "description": "Fingerprint Protection (may break sites)", + "message": "指紋保護(可能會阻礙網站使用)" + }, + "firstsync": { + "description": "It appears you haven't synced your settings to your Google account yet.\r\nScriptSafe is about to sync your current settings to your Google account.\r\nClick on 'OK' if you want to continue.\r\nIf not, click 'Cancel', and on the other device with your preferred settings, update ScriptSafe and click on OK when you are presented with this message.", + "message": "您似乎沒有同步您的設定到您的Google帳戶。\r\nScriptSafe將要同步您的設定到您的Google帳戶。\r\n如果您想繼續,請點擊“OK”。\r\n否則,請點擊“Cancel”,在其他裝置上使用你喜愛的設定,更新ScriptSafe,當您看到這段字,點擊OK。" + }, + "forcesyncexport": { + "description": "Do you want to sync your current settings to your Google Account?\r\nNote: please do not press this frequently; there is a limit of 10 per minute and 1,000 per hour.", + "message": "您想同步當前設定到您的Google帳戶?\r\n注意:請不要頻密地按此鍵;有每分鐘10次和每小時1000次的限制。" + }, + "forcesyncimport": { + "description": "Do you want to import the synced settings from your Google Account to this device?", + "message": "您想從您的Google賬戶匯入同步設定嗎?" + }, + "forever": { + "description": "Forever", + "message": "Forever" + }, + "hotkeys": { + "description": "Hotkeys:", + "message": "熱鍵:" + }, + "generalsettings": { + "description": "General Settings", + "message": "一般設定" + }, + "gamepad": { + "description": "Block Gamepad Enumeration:", + "message": "封鎖遊戲手柄計算:" + }, + "gamepaddesc": { + "description": "prevent having devices detected via the Gamepad API", + "message": "防止通過遊戲手柄API檢測到裝置" + }, + "availablehotkeys": { + "description": "Available hotkey actions", + "message": "可用熱鍵的操作" + }, + "hotkeystoggle": { + "description": "Temporarily allow/block all resources for a current tab", + "message": "暫時允許或封鎖當前標籤頁的所有資源" + }, + "hotkeysremove": { + "description": "Remove temporary permissions for a current tab", + "message": "移除當前標籤頁的臨時權限" + }, + "hotkeysremoveall": { + "description": "Remove all temporary permissions", + "message": "移除所有臨時權限" + }, + "hotkeyspage": { + "description": "Configure ScriptSafe hotkeys", + "message": "設置ScriptSafe熱鍵" + }, + "hotkeysinst": { + "description": "click on Keyboard Shortcuts", + "message": "點擊快捷鍵" + }, + "listallsettings": { + "description": "List All Settings", + "message": "列出所有設定" + }, + "ignoredallow": { + "description": "Ignored Allow", + "message": "忽略允許" + }, + "importsuccess": { + "description": "The latest settings have been successfully downloaded!", + "message": "最新的設定已成功下載!" + }, + "importsuccesstitle": { + "description": "Settings Downloaded!", + "message": "設定已下載!" + }, + "groupallsettings": { + "description": "Group All Settings", + "message": "組合所有設定" + }, + "hashchecking": { + "description": "Remove Possible Hash Tracking:", + "message": "移除可能存在的雜湊追蹤:" + }, + "hashcheckingdesc": { + "description": "remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1)", + "message": "移除使用雜湊的跟踪標記,這些標記有屬性和值(例如#xtor=RSS-1)" + }, + "hide": { + "description": "Hide", + "message": "隱藏" + }, + "import": { + "description": "Import", + "message": "匯入" + }, + "importexport": { + "description": "Import / Restore Settings", + "message": "匯入/恢復設定" + }, + "importsuccessoptions": { + "description": "Settings imported successfully", + "message": "設定成功匯入" + }, + "importsuccesscond": { + "description": "Settings imported successfully, except the following (empty value or unrecognized name):", + "message": "設定成功匯入,除了以下(空值或無法識別的名稱):" + }, + "importsuccesssync": { + "description": "Settings imported successfully and syncing in 10 seconds", + "message": "成功匯入並在10秒內同步設定" + }, + "interval": { + "description": "Every x Minutes", + "message": "Every x Minutes" + }, + "keyboard": { + "description": "Reduce Keyboard Fingerprinting (for advanced users):", + "message": "減少鍵盤指紋(進階用戶):" + }, + "keyboarddesc": { + "description": "make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable)", + "message": "令按鍵時序更隨機增加隱匿性(注意:增加了按鍵之間的隨機延遲;如不可接受,請停用此設定)" + }, + "browserplugins": { + "description": "Block Browser Plugin Enumeration:", + "message": "Block Browser Plugin Enumeration:" + }, + "browserpluginsdesc": { + "description": "prevent sites from reading your browser plugin details", + "message": "prevent sites from reading your browser plugin details" + }, + "linktarget": { + "description": "Page Link Opening Behaviour:", + "message": "頁面連結的打開方式:" + }, + "linktargetdesc": { + "description": "modifies how all links are opened", + "message": "修改所有連結的打開方式" + }, + "loosesamedomain": { + "description": "Loose - allow same domain and subdomains", + "message": "寬鬆 - 允許相同的網域和子網域" + }, + "minutes": { + "description": "Minutes", + "message": "Minutes" + }, + "mode": { + "description": "Default Mode", + "message": "預設模式" + }, + "newtab": { + "description": "New Tab", + "message": "新頁面標籤" + }, + "noexternal": { + "description": "This tab has loaded no external resources", + "message": "此頁面標籤並無加載任何外部資源" + }, + "notfiltered": { + "description": "Not filtered", + "message": "未經過濾" + }, + "nowebrtc": { + "description": "This browser does not support WebRTC protection", + "message": "這瀏覽器不支援WebRTC保護" + }, + "off": { + "description": "-Off-", + "message": "-關閉-" + }, + "onlyunwhitelisted": { + "description": "Only on Unwhitelisted Domains", + "message": "只有在未被列入允許名單的網域" + }, + "options": { + "description": "Options", + "message": "選項" + }, + "paranoia": { + "description": "Paranoia Mode:", + "message": "進取模式:" + }, + "paranoiadesc": { + "description": "block allowed domains on unlisted domains", + "message": "封鎖已允許但未列出的網域" + }, + "pastesettings": { + "description": "Paste in settings and try again", + "message": "貼上設定,然後再試一次" + }, + "preservesamedomain": { + "description": "Respect Same-Domain:", + "message": "保持同網域:" + }, + "preservesamedomaindesc": { + "description": "preserve same-domain elements", + "message": "保持同網域元素" + }, + "privacy": { + "description": "Privacy Settings", + "message": "隱私設定" + }, + "random": { + "description": "Random", + "message": "隨機" + }, + "rating": { + "description": "Show Rating Button:", + "message": "顯示評分按鈕:" + }, + "ratingbtn": { + "description": "Rating", + "message": "評分" + }, + "ratingdesc": { + "description": "if ticked, adds rating button under domains in tab popup", + "message": "如果勾選,增加評分按鈕彈在選項卡下的網域" + }, + "recentlyupdated": { + "description": "ScriptSafe was recently updated/reloaded.You will need to either refresh this tab, create a new tab, or restart your browser in order for ScriptSafe to work.", + "message": "ScriptSafe最近已更新或重新載入。您將需要重新載入此標籤頁、開啟新的標籤頁,或重新啟動瀏覽器以令ScriptSafe正常運作。" + }, + "referrer": { + "description": "Block Click-Through Referrer:", + "message": "封鎖點擊參照位址:" + }, + "referrerdesc": { + "description": "blocks referrer information when clicking on third-party links (note: setting this to On All Domains may cause issues (e.g. thumbnails in Tweetdeck))", + "message": "點擊第三方連結時封鎖參照位址資訊(注意:向所有網域作此設定,可能會引致問題(例如,在TweetDeck的縮圖))" + }, + "referrerspoof": { + "description": "Referrer Spoof:", + "message": "假參照位址:" + }, + "referrerspoofdesc": { + "description": "warning: if enabled, may break some sites (e.g. logging in)", + "message": "警告:如果啟用,可能令一些網站無法正常運作(如登入)" + }, + "refresh": { + "description": "Auto-Refresh Page:", + "message": "自動重新載入頁面:" + }, + "refreshdesc": { + "description": "auto-refresh page after list change", + "message": "名單變更後自動重新載入頁面" + }, + "relaxed": { + "description": "Relaxed", + "message": "寬鬆" + }, + "request": { + "description": "Every Request", + "message": "Every Request" + }, + "restoredefault": { + "description": "Restore Default Settings", + "message": "Restore Default Settings" + }, + "restoredefault2": { + "description": "Restore Default Settings + Clear All Lists", + "message": "Restore Default Settings + Clear All Lists" + }, + "restoredefaultconfirm": { + "description": "Are you sure you want to restore the default settings? This will NOT clear your lists.", + "message": "Are you sure you want to restore the default settings? This will NOT clear your lists." + }, + "restoredefaultconfirm2": { + "description": "Are you sure you want to restore the default settings AND clear all of your lists?", + "message": "Are you sure you want to restore the default settings AND clear all of your lists?" + }, + "revoketemp": { + "description": "Revoke Page Temporary Permissions", + "message": "撤銷頁面的臨時權限" + }, + "revoketempall": { + "description": "Revoke All Temporary", + "message": "撤銷所有臨時(權限)" + }, + "same": { + "description": "Same Document", + "message": "同一HTML文本" + }, + "sametab": { + "description": "Same Tab", + "message": "同一標籤頁" + }, + "save": { + "description": "Save", + "message": "儲存" + }, + "savetxt": { + "description": "Save as Text File", + "message": "另存為文字檔案" + }, + "sections": { + "description": "Sections", + "message": "段" + }, + "settingsall": { + "description": "select all", + "message": "全選" + }, + "settingsimport": { + "description": "Copy and paste the settings you want to import into ScriptSafe into this box then click on the Import button.", + "message": "複製和貼上您想匯入到ScriptSafe的設定到此方格,然後按匯入按鈕。" + }, + "settingssave": { + "description": "Settings saved", + "message": "儲存設定" + }, + "settingssavesync": { + "description": "Settings saved and syncing in 10 seconds", + "message": "設定已儲存並會在10秒內同步" + }, + "showcontext": { + "description": "Show in Context Menu:", + "message": "顯示在快顯功能表:" + }, + "ssdisabled": { + "description": "ScriptSafe is disabled", + "message": "ScriptSafe被停用" + }, + "strict": { + "description": "Strict", + "message": "嚴格" + }, + "strictsamedomain": { + "description": "Strict - allow same domain only", + "message": "嚴格 - 只允許同一個網域" + }, + "support": { + "description": "To support development, click the heart :)", + "message": "支持開發,點擊心心 :)" + }, + "syncdisable": { + "description": "You have enabled auto-syncing. In order to prevent erasing your previously synced data (if any), please click on Sync Settings FROM Google Account.", + "message": "您已啟用自動同步。為了避免抹掉您之前同步的資料(如有),請從Google帳戶按同步設定。" + }, + "syncimport": { + "description": "Sync Settings FROM Google Account", + "message": "從Google帳戶同步設定" + }, + "syncexport": { + "description": "Sync Settings TO Google Account", + "message": "同步設定到Google帳戶" + }, + "syncdetect": { + "description": "ScriptSafe has detected that you have settings synced on your Google account!\r\nClick on 'OK' if you want to import the settings from your Google Account.", + "message": "ScriptSafe已檢測到您已在Google帳戶同步您的設定!\r\n如果您想從您的Google帳戶匯入設定,請按“OK”。" + }, + "syncdisabled": { + "description": "Syncing has been disabled to prevent overwriting your already synced data.\r\nFeel free to go to the Options page at any time to sync your settings (make a backup of your settings if necessary).", + "message": "同步已被停用以防止抹掉您已經同步的資料。\r\n隨意進入選項頁面,隨時同步您的設定(如有必要請設定備份)。" + }, + "syncfromnotify": { + "description": "Show Import Sync Notification:", + "message": "顯示匯入同步通知:" + }, + "syncfromnotifydesc": { + "description": "show popup when settings synced from your Google Account", + "message": "當從您的Google賬戶同步設定時,顯示彈出框框" + }, + "syncnotify": { + "description": "Show Sync Notification:", + "message": "顯示同步通知:" + }, + "syncnotifydesc": { + "description": "show popup when settings synced to your Google Account", + "message": "當設定同步到您的Google帳戶時,顯示彈出框框" + }, + "syncnotsupported": { + "description": "Your current version of Google Chrome does not support settings syncing. Please try updating your Chrome version and try again.", + "message": "您當前的Google瀏覽器版本不支持設定同步。請嘗試更新您的Chrome瀏覽器版本,然後再試一次。" + }, + "temp": { + "description": "Temporary", + "message": "臨時" + }, + "timezone": { + "description": "Spoof Timezone:", + "message": "假時區:" + }, + "timezonedesc": { + "description": "spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.", + "message": "假或隨機的時區。注意:如果啟用,在Gmail回覆郵件可能會受干擾。" + }, + "trust": { + "description": "Trust", + "message": "相信" + }, + "trustlow": { + "description": "trust", + "message": "相信" + }, + "uaspoofallow": { + "description": "Apply to whitelisted domains as well", + "message": "同時適用於列入允許名單的網域名" + }, + "updatedisable": { + "description": "Are you sure you want to disable any future update notifications like this one from appearing?\r\nYou can always re-allow update notifications by going to the ScriptSafe Options page and ticking the box beside Show Update Popup.", + "message": "您確定之後要停用任何像今次的更新通知?\r\n只要在ScriptSafe的選項頁勾選旁邊的方格,您就可以隨時重新允許更新通知。" + }, + "updatedisablemessage": { + "description": "Update notifications disabled", + "message": "停用更新通知" + }, + "updatenotify": { + "description": "Show Changelog on Update:", + "message": "更新時顯示更新日誌:" + }, + "unwanted": { + "description": "Unwanted", + "message": "不需要" + }, + "updatenotifydesc": { + "description": "show changelog page when ScriptSafe is updated", + "message": "當ScriptSafe已更新,顯示更新日誌" + }, + "url": { + "description": "Domain", + "message": "網域" + }, + "urldesc": { + "description": "Enter a domain or expression (click 'Help' for more info)", + "message": "輸入網域或公式(按「幫助」以獲更多資訊)" + }, + "useragentspoof": { + "description": "User-Agent Spoof:", + "message": "假用戶代理:" + }, + "useragentspoofdesc": { + "description": "spoofs your user-agent (browser and OS)", + "message": "虛構您的用戶代理(瀏覽器和操作系統)" + }, + "userref": { + "description": "Enter an address to set as your referrer value for all sites", + "message": "輸入網址設定為所有網站的參照位址" + }, + "utm": { + "description": "Remove Google Analytics (UTM) Tracking:", + "message": "移除Google分析(UTM)追踪:" + }, + "utmdesc": { + "description": "remove Google Analytics (UTM) tracking tokens", + "message": "移除Google分析(UTM)跟踪標記" + }, + "webbugs": { + "description": "Remove Webbugs:", + "message": "移除Webbugs:" + }, + "webbugsdesc": { + "description": "remove invisible third-party elements", + "message": "移除看不見的第三方元素" + }, + "webgl": { + "description": "Block WebGL Fingerprinting:", + "message": "封鎖WebGL指紋識別:" + }, + "webgldesc": { + "description": "prevent fingerprinting via the WebGL API", + "message": "避免通過WebGL API的指紋識別" + }, + "webrtcdevice": { + "description": "Block Device Enumeration:", + "message": "封鎖設備計算:" + }, + "webrtcdevicedesc": { + "description": "prevent having hardware devices detected via the WebRTC API", + "message": "避免通過WebRTC API檢測到硬件設備" + }, + "webrtc": { + "description": "WebRTC Protection:", + "message": "WebRTC保護:" + }, + "webrtcdesc": { + "description": "prevent IP address leakage", + "message": "防止IP地址洩漏" + }, + "webvr": { + "description": "Block WebVR Enumeration:", + "message": "封鎖WebVR手柄計算:" + }, + "webvrdesc": { + "description": "prevent having devices detected via the WebVR API", + "message": "防止通過WebVR API檢測到裝置" + }, + "blackbind": { + "description": "+ Blacklist", + "message": "+ 黑名單" + }, + "whitebind": { + "description": "+ Whitelist", + "message": "+ 允許名單" + }, + "blacklist": { + "description": "Blacklist", + "message": "黑名單" + }, + "whitelist": { + "description": "Whitelist", + "message": "允許名單" + }, + "blacklisted": { + "description": "Blacklisted", + "message": "列入黑名單" + }, + "whitelisted": { + "description": "Whitelisted", + "message": "列入允許名單" + }, + "blacklistlow": { + "description": "blacklist", + "message": "黑名單" + }, + "whitelistlow": { + "description": "whitelist", + "message": "允許名單" + }, + "blacklistmove": { + "description": "Move to Blacklist", + "message": "移到黑名單" + }, + "whitelistmove": { + "description": "Move to Whitelist", + "message": "移到允許名單" + }, + "whitelistblacklist": { + "description": "Whitelist / Blacklist", + "message": "允許名單或黑名單" + }, + "xml": { + "description": "XML HTTP Request Handling:", + "message": "XML HTTP請求處理:" + }, + "xmlall": { + "description": "Control All Requests", + "message": "控制所有請求" + }, + "xmlcross": { + "description": "Control Cross-Domain Requests (allow Same-Domain)", + "message": "控制跨網域請求(允許同網域)" + }, + "xmldesc": { + "description": "control XML HTTP Requests", + "message": "控制XML HTTP請求" + } +} \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_metadata/verified_contents.json b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_metadata/verified_contents.json new file mode 100644 index 0000000..ea031d0 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/_metadata/verified_contents.json @@ -0,0 +1 @@ +[{"description":"treehash per file","signed_content":{"payload":"eyJjb250ZW50X2hhc2hlcyI6W3siYmxvY2tfc2l6ZSI6NDA5NiwiZGlnZXN0Ijoic2hhMjU2IiwiZmlsZXMiOlt7InBhdGgiOiJfbG9jYWxlcy9jcy9tZXNzYWdlcy5qc29uIiwicm9vdF9oYXNoIjoiX3VvNlZ5V1VBMGZGQWpkelpxWDNRR0JaeTBVT3QxM2VhQ09iM1ZqVHFJNCJ9LHsicGF0aCI6Il9sb2NhbGVzL2RlL21lc3NhZ2VzLmpzb24iLCJyb290X2hhc2giOiJfX2JVVG4ySGpWX0ktYThYd3ZUTXRrM1NjOFJkbmwtUXZ4b2lXUUxqTEIwIn0seyJwYXRoIjoiX2xvY2FsZXMvZW4vbWVzc2FnZXMuanNvbiIsInJvb3RfaGFzaCI6IlEzSFdRMUs5bjhtZzdPTWRPMEFQcDdRQW5DWjVGYnA2MktrZjF1MzhDSHMifSx7InBhdGgiOiJfbG9jYWxlcy9lbl9HQi9tZXNzYWdlcy5qc29uIiwicm9vdF9oYXNoIjoidlFwS1VzSVpBTUNZS2NFNktfc3F0WERsQkFnN095NHNCQ2ppRDVaS1lvTSJ9LHsicGF0aCI6Il9sb2NhbGVzL2VuX1VTL21lc3NhZ2VzLmpzb24iLCJyb290X2hhc2giOiJRM0hXUTFLOW44bWc3T01kTzBBUHA3UUFuQ1o1RmJwNjJLa2YxdTM4Q0hzIn0seyJwYXRoIjoiX2xvY2FsZXMvZXMvbWVzc2FnZXMuanNvbiIsInJvb3RfaGFzaCI6IlJqa0VFMVBHU0lsbFp3WmVzLWpXNU5kWFVJMUpRX1VwRXNGYlFiUTZxem8ifSx7InBhdGgiOiJfbG9jYWxlcy9mci9tZXNzYWdlcy5qc29uIiwicm9vdF9oYXNoIjoiV3daWWxSS3RWRXpqS2h2aDZzQk8wQ1JyUVFybE1kcWZ4bXg4R0hHYWoyNCJ9LHsicGF0aCI6Il9sb2NhbGVzL2h1L21lc3NhZ2VzLmpzb24iLCJyb290X2hhc2giOiJhTDlsSDdTUUNkU2FTT3lYaEEtU1ViOUhXc0VQTHlrMW16YjMtSldVYWFRIn0seyJwYXRoIjoiX2xvY2FsZXMvaXQvbWVzc2FnZXMuanNvbiIsInJvb3RfaGFzaCI6ImlJWWotYzRfSVBpcGNaakN2eC1Zcm1GNG1MYnVVWDdoZWU2SmEwdzVEY1UifSx7InBhdGgiOiJfbG9jYWxlcy9qYS9tZXNzYWdlcy5qc29uIiwicm9vdF9oYXNoIjoiSXYyX1M5RG16M0tkdUVuTzFmZmdNamhEcUFDNTFsRWNidUtuaGtlOXQtcyJ9LHsicGF0aCI6Il9sb2NhbGVzL2tvL21lc3NhZ2VzLmpzb24iLCJyb290X2hhc2giOiJvdmxKS2R5X3FTeU92VW1nUHhqekZuRkVQQ1J4R0NzLUgxT19tSy1hMWhjIn0seyJwYXRoIjoiX2xvY2FsZXMvbHYvbWVzc2FnZXMuanNvbiIsInJvb3RfaGFzaCI6IllQRkc1OHVZOVJGNjYwcVFLY25vUmJ1QVJ3eXJsSTJGaXJ5OV9TeUcwZU0ifSx7InBhdGgiOiJfbG9jYWxlcy9ubC9tZXNzYWdlcy5qc29uIiwicm9vdF9oYXNoIjoiR3RMZlAzTHlVWFBVSkUteG1KeF9vWGJmZUxuR1p6MUdncTNDVmNVczh3QSJ9LHsicGF0aCI6Il9sb2NhbGVzL3BsL21lc3NhZ2VzLmpzb24iLCJyb290X2hhc2giOiJTUzFvUTlyNk1ESUtjd0N0OEhHbmFUZ3RfcFgxaGJndGtudDdtemtfWVBNIn0seyJwYXRoIjoiX2xvY2FsZXMvcm8vbWVzc2FnZXMuanNvbiIsInJvb3RfaGFzaCI6InhTa2VrMVFwRl9uMFZGZkZnaENXOW04RVI4ZTFaNS1kUlBHSEw2SzRNUGMifSx7InBhdGgiOiJfbG9jYWxlcy9ydS9tZXNzYWdlcy5qc29uIiwicm9vdF9oYXNoIjoiWllDNEdPTHYxVFpWVWIzcVZvSW5HTVZoQk1EZXE3N3M2d0dVU2gxSXo3NCJ9LHsicGF0aCI6Il9sb2NhbGVzL3N2L21lc3NhZ2VzLmpzb24iLCJyb290X2hhc2giOiJQZjRTc29RRnBZdGlNOE51M0hmUWtTX2J0QV9FQV81R2VPbDNSeUdFTDA4In0seyJwYXRoIjoiX2xvY2FsZXMvemhfQ04vbWVzc2FnZXMuanNvbiIsInJvb3RfaGFzaCI6IkU1Ui0yTEFDT195WmYtQjZ4bjdBWm1qdC02Y1dtZzVoV2ZXRDFic0IwckkifSx7InBhdGgiOiJfbG9jYWxlcy96aF9UVy9tZXNzYWdlcy5qc29uIiwicm9vdF9oYXNoIjoibHdpSWltaFZRTDFIS2ZZMXQ3VzUzU1JUM2daTzc1YWU3Z1dKbldIckVSayJ9LHsicGF0aCI6ImNzcy9ib290c3RyYXAtdGhlbWUubWluLmNzcyIsInJvb3RfaGFzaCI6IlZMN3ZjTTRTc2tpM09VUnZ1RUJjT3U5RTRRTGw4QUN3blhhVVhRTXFfM2sifSx7InBhdGgiOiJjc3MvYm9vdHN0cmFwLm1pbi5jc3MiLCJyb290X2hhc2giOiJFQjJxVXlxdUN4d1l1WmJTd0xrNlUxYjcyS2VqOUNxMko4Nk1wSS01aUFrIn0seyJwYXRoIjoiY3NzL2dseXBoLmNzcyIsInJvb3RfaGFzaCI6IkhRdmFaWXJpVkl2TGw2bXBLOW11cElkTGhPcG1raTRZakRnT2FoYVc2NU0ifSx7InBhdGgiOiJjc3Mvb3B0aW9ucy5jc3MiLCJyb290X2hhc2giOiJNTFRhVlRxektmVzdtMHVWcHMyaDBjVHcyODNVUXlseERQLWR2TWd4MkdNIn0seyJwYXRoIjoiY3NzL3BvcHVwLmNzcyIsInJvb3RfaGFzaCI6IkNIWXFVbkRoZUsyenVFMWdwNjlhVTdwbTBEcjA5VmVQeDZ2Vy1yZWhzQUkifSx7InBhdGgiOiJjc3MvcmVjZW50cy5jc3MiLCJyb290X2hhc2giOiJDUkM0cEkwc0VTTXQ3Z21wOUg2OGFlNGZoTERjY0tzdDF1a2lCN3Q4eUlVIn0seyJwYXRoIjoiZm9udHMvZ2x5cGhpY29ucy1oYWxmbGluZ3MtcmVndWxhci5lb3QiLCJyb290X2hhc2giOiJuSmNmUjFLc3JzTkxwaVhSMENRYlZPYnZIdHFHQlp6V0Q5dmlDcjloV2NrIn0seyJwYXRoIjoiZm9udHMvZ2x5cGhpY29ucy1oYWxmbGluZ3MtcmVndWxhci5zdmciLCJyb290X2hhc2giOiJMQ1E5UHRaSnU0TVpEd2FsODhVQ2J1UEZqcHF6Q0JoSEJ5Qnl3MHFIcnVnIn0seyJwYXRoIjoiZm9udHMvZ2x5cGhpY29ucy1oYWxmbGluZ3MtcmVndWxhci50dGYiLCJyb290X2hhc2giOiJFTlR6NlNlWmFNZFotN0Z4Q1ZTSjJqekltR3Jldm90YnZIVE5wdFBTTFRvIn0seyJwYXRoIjoiZm9udHMvZ2x5cGhpY29ucy1oYWxmbGluZ3MtcmVndWxhci53b2ZmIiwicm9vdF9oYXNoIjoiYy1JY2JZbDZXV1NVUXFmZlpxRkR1UDBPVHBTZG1aY2FRYXJ4LXF1RzhoOCJ9LHsicGF0aCI6ImZvbnRzL2dseXBoaWNvbnMtaGFsZmxpbmdzLXJlZ3VsYXIud29mZjIiLCJyb290X2hhc2giOiIxMHg5dUgxQm85d1V5MzhrMUFhYXdvZENaY3hQUnZ0YlFhcDJ1cW93VVVzIn0seyJwYXRoIjoiZ3BsLnR4dCIsInJvb3RfaGFzaCI6IlFuc1U3WkwxQXdDbXNaWGkyaE9YWjVMclo1R1hnZjkyOFdsRi1sUTRQYWsifSx7InBhdGgiOiJodG1sL2JhY2tncm91bmQuaHRtbCIsInJvb3RfaGFzaCI6IllNdVdheV8wNW1DQlM3SEhNS3BtMnFTVDFvWEpoLTNqRm84REN2T2pHRHMifSx7InBhdGgiOiJodG1sL29wdGlvbnMuaHRtbCIsInJvb3RfaGFzaCI6IjVtaE5fT2w4V0hNM3RsMURDUHRvWXNmM25WUEJiUm1TVVdtak1xcHlWbDAifSx7InBhdGgiOiJodG1sL3BvcHVwLmh0bWwiLCJyb290X2hhc2giOiJaV01VMEFKSmVVeHdIalRob3A5bmFwVHRCTVFkNE8teG1lQjQ1NVh3aVRBIn0seyJwYXRoIjoiaHRtbC9yZWNlbnRzLmh0bWwiLCJyb290X2hhc2giOiJsaFZGNE05cUNLMmF4VjlmaHB5dlR4RmtQbEJZUmJSNFlYWGxfN3k2OFFvIn0seyJwYXRoIjoiaHRtbC91cGRhdGVkLmh0bWwiLCJyb290X2hhc2giOiJzQlBfZTZ2bnAwaWttdkw2WHBtOXFnMHQxNmNsS3pTT29tZVJvWG5nc3lrIn0seyJwYXRoIjoiaW1nL0ljb25BbGxvd2VkLnBuZyIsInJvb3RfaGFzaCI6InZhNzQ3czJuUEZXblFRTm95ZlQ5YWlqUUNPLTZBRWFPNUkwZE9XbEIzejgifSx7InBhdGgiOiJpbWcvSWNvbkRpc2FibGVkLnBuZyIsInJvb3RfaGFzaCI6Im9CSzlDcF9MNFNQczVXWXJJUkVOMUFwUEN0cVVPOTdCNmRac0N0UFliWk0ifSx7InBhdGgiOiJpbWcvSWNvbkZvcmJpZGRlbi5wbmciLCJyb290X2hhc2giOiIyQWdTM1pZSlF5TmNnSXkwdFZzX3ZvbU9wdXZsWnc4ZzIyaGRRbDk4VGdnIn0seyJwYXRoIjoiaW1nL0ljb25UZW1wLnBuZyIsInJvb3RfaGFzaCI6IndYLTJwSjI1Vk4xNG5VRnAwT01CVWlJeFprVlM1dndicWxKU1JZY3IzNHcifSx7InBhdGgiOiJpbWcvaGVhcnRiaWcucG5nIiwicm9vdF9oYXNoIjoiMUo5aGFmYXo0SXBXUXN2M3ZPOHlxeXpLeERIQ2VrVE00OGJpd0VqT3dNZyJ9LHsicGF0aCI6ImltZy9oZWFydHNtYWxsLnBuZyIsInJvb3RfaGFzaCI6Inh0QzNVdGMxZ3FkM0phREI3VE1mN1Ewd1ZyQmdYUmJKU0tsRG1HV2xSTFkifSx7InBhdGgiOiJpbWcvaWNvbjEyOC5wbmciLCJyb290X2hhc2giOiJVWEcyRFlwTmN4UUliR3VDSkFaZHA2UHNCTmh5MDVKZ1pWSE52Qi1oeGlBIn0seyJwYXRoIjoiaW1nL2ljb24xNi5wbmciLCJyb290X2hhc2giOiJqWTFlVmFfVVFlT21SVlBrWHJSOWx6YjRwWkJ2Zkc5dmpjT0ZaRExsWXVNIn0seyJwYXRoIjoiaW1nL2ljb24yNC5wbmciLCJyb290X2hhc2giOiJYYnpuTnRKV2lCRnpDaE9QdGdTLWI4b05TUFZnQUhpX2kzUFJVWjlaQ1FNIn0seyJwYXRoIjoiaW1nL2ljb24zMi5wbmciLCJyb290X2hhc2giOiJZLThTZHd4VU8xT3RJMV9zOVh6TkxER2hyX0xZLXdXdFZHOTJlZ3RBcW40In0seyJwYXRoIjoiaW1nL2ljb240OC5wbmciLCJyb290X2hhc2giOiJrT1NpZ2xzSUxCMjRsOFpOckJWbUo3YjY3dElEUVFOWjBsVUJMb2JHd2pJIn0seyJwYXRoIjoianMvYm9vdHN0cmFwLm1pbi5qcyIsInJvb3RfaGFzaCI6IlhSSkFRM0poUzBqSVduUWgxRFZIc09wRk9WWjNrcGduTkFoSFdfWS1STE0ifSx7InBhdGgiOiJqcy9jb21tb24uanMiLCJyb290X2hhc2giOiJseGN0WldoZGtScE1WWHNWcGtEMjQ1b0ZYTHROS25ZR29JVm5pZXV1SmR3In0seyJwYXRoIjoianMvanF1ZXJ5LmpzIiwicm9vdF9oYXNoIjoiNHhKSlgxeW5yZGFYalZIelc1VUZmZE9zb3hfQ3YxeEVNeWFuRkNreEtRZyJ9LHsicGF0aCI6ImpzL29wdGlvbnMuanMiLCJyb290X2hhc2giOiJtMTdPM3V1Qy00eGtDZ3BySVBrVDVCWkxwY2FEdTBlZngzQ29lTENMNzdZIn0seyJwYXRoIjoianMvcGFrby5qcyIsInJvb3RfaGFzaCI6InFZcjAtOWJLQUloZ0tnVnl6SW93eVVFUUNGbHlrTWFGbURwUU9pUHlWbUUifSx7InBhdGgiOiJqcy9wb3B1cC5qcyIsInJvb3RfaGFzaCI6IkxEM2Zwb0txdEFXajBfQXI5T2dxX205cS1nUy1aN3hLeDhvX2ZkSTlnRjQifSx7InBhdGgiOiJqcy9yZWNlbnRzLmpzIiwicm9vdF9oYXNoIjoiUW1IcW9GQUxfNHNhMFJhWi1jUXUtRmNpNDhud1V6V2JkQjdlTG03MmNKOCJ9LHsicGF0aCI6ImpzL3NjcmlwdHNhZmUuanMiLCJyb290X2hhc2giOiJqMThJZGZaQnVDMW9VMC0xNzlqbmVtT3RrQVJ6b1FfaC1kdzJRMWoxVWI4In0seyJwYXRoIjoianMvc3MuanMiLCJyb290X2hhc2giOiJ6NnB2bnlPVDdSUGJJamVRSVNWdUJJekdkQzRYWGRCRjdMV2p2RUhadmcwIn0seyJwYXRoIjoianMvdXBkYXRlZC5qcyIsInJvb3RfaGFzaCI6ImlNSG5MRjVrTXY3WHFaa2pqUWJqS0NMcHhZWG1FNE1CYzhxdjJKTk41bjQifSx7InBhdGgiOiJqcy93ZWJydGN0ZXN0LmpzIiwicm9vdF9oYXNoIjoia3UxX2FieDlwSV9sVk1LbmxQTDRaNGFBZHZpUk9tZ0QzeXZiTTc4Vks1YyJ9LHsicGF0aCI6ImpzL3lveW8uanMiLCJyb290X2hhc2giOiI4SjkxRVZ2ZGliRXAwRnMxeHJwWVpxdWpPTXVWUUJGZ015SVpBRjBZZzgwIn0seyJwYXRoIjoibWFuaWZlc3QuanNvbiIsInJvb3RfaGFzaCI6IkNKdGxtSUZZbXl2dGZrZHVwXzlJa1BGSkpxQ0U0YWpIWUhTcWlHQlc5Z2MifV0sImZvcm1hdCI6InRyZWVoYXNoIiwiaGFzaF9ibG9ja19zaXplIjo0MDk2fV0sIml0ZW1faWQiOiJvaWlnYm1uYWFkYmtmYm1wYmZpamxmbGFoYmRiZGdkZiIsIml0ZW1fdmVyc2lvbiI6IjEuMC45LjMiLCJwcm90b2NvbF92ZXJzaW9uIjoxfQ","signatures":[{"header":{"kid":"publisher"},"protected":"eyJhbGciOiJSUzI1NiJ9","signature":"Oi9LR0PHd6IaJTyGHIvzyXsr-sw_FbJi2tnASRKSHjlopRnFkD5byiy5CCkXKoFOeQmHSUAzs3i72arTJCkmX7B-YsWElAyociyd9ZjAbfRy0AuU6opsc6prx9FJhl9eKFIDyTpRhfi8tJzPgTSy9Ysyqfn4gD8ZoQn4CcWjObs"},{"header":{"kid":"webstore"},"protected":"eyJhbGciOiJSUzI1NiJ9","signature":"DlShAie07b14Tgv2RT2wQ2Rs3BsQ_xs7rzeNNUdNErkk6v6qjmQTqCaqVtaOGa3ZjP9ysPYtb-0XZvQfM5Zbb0DbHsklvZqWVu9LwQELELobimq4F9ELJM1Ervk_zoxkbCvwl1lIpKbh-3FmHYKqRCIEeJYn596T4h9qSE07yvGV-rsn0tXACG-mYdVzKAzIkmFnfkkSPXJxxwZutqw3vpD7Ft0Qv-QsGA3MaTSosuwhYPS3XEY0-CIBAc-HPXNPdxrAIhhx-ufEtvjO5_NFdCaiKGDzHc90_FC87FWPYDD15LH3ps-OlxgBDn92sYha3MVk7h7Vh9DT8bNG9SHAyw"}]}}] \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/bootstrap-theme.min.css b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/bootstrap-theme.min.css new file mode 100644 index 0000000..e52d5a1 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/bootstrap-theme.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} +/*# sourceMappingURL=bootstrap-theme.min.css.map */ \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/bootstrap.min.css b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/bootstrap.min.css new file mode 100644 index 0000000..7aef5c8 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/bootstrap.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/glyph.css b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/glyph.css new file mode 100644 index 0000000..1638afd --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/glyph.css @@ -0,0 +1 @@ +@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"} \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/options.css b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/options.css new file mode 100644 index 0000000..bc4efea --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/options.css @@ -0,0 +1,22 @@ +body { padding: 25px 0; } +.tab { display: none; } +.tab.active { display: block; } +.table { margin-bottom: 0px; } +.table tr:first-child td { border-top: none; padding-top: 10px; } +.table tr:last-child td { padding-bottom: 10px; } +h3 { margin: 0px !important; } +#title, .i18_support { color: green; font-size: 30px; font-weight: bold; } +#title:hover { color: blue; } +.i18_support { color: #000; font-size: 18px; } +#message { z-index: 999; color: #000; margin: 5px; position: fixed; top: 0; right: 0; padding: 5px; font-weight: bold; } +.sshidden, #message { display: none; } +.listentry, .clear { clear: both; } +.listentry { border-bottom: 1px solid #eee; padding: 2px; font-weight: bold; } +.listentry:hover { background-color: #f5f5f5; } +.right { float: right; } +.entryoptions { float: right; font-size: 11px; } +.list, textarea { min-height: 300px; max-height: 300px; overflow-y: auto; } +.fp-list { min-height: 100px; max-height: 100px; } +.fp-list .entryoptions { padding-top: 3px; } +.sectionheading, .sectionheading h4 { margin-bottom: 0px; } +.row-offcanvas { display: none; } \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/popup.css b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/popup.css new file mode 100644 index 0000000..0f96b01 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/popup.css @@ -0,0 +1,356 @@ +/* Global */ +html { width: 575px; } +html, body { word-wrap: break-word; } +html, body, td, th { font-family: arial, sans-serif; } +body { margin: 0px 5px; padding: 0px; font-size: 100%; background-color: #fff; } +#container, table { width: 100%; } +#container { font-size: 0.75em; } +td, th { text-align: left; } +td { vertical-align: top; } +th { padding-bottom: 5px; } +#header { width: 100%; padding: 5px 0; margin-bottom: 10px; position: fixed; background-color: #fff; padding-left: 5px; margin-left: -5px; border-bottom: 2px solid #ccc; } +#header a#pop_webstore { font-weight: bold; color: #000; text-decoration: none; } +#header a#pop_webstore:hover { text-decoration: underline; } +#header div { float: right; margin-right: 7px; } +#pop_close { margin-right: 5px; } +#parent { padding-bottom: 5px; } +table { padding-top: 35px; } + +/* Tab Options */ +#parent { text-align: right; padding-right: 3px; } +#parent > div { border-bottom: none; display: inline-block; } + +/* Resource List */ +.thirds { vertical-align: top; } +.thirditem span a, fpitem span a { text-decoration: none; font-weight: bold; } +.thirditem span a:hover, .fpitem span a:hover { text-decoration: underline; } +.allowed, .blocked, .domainname { padding: 0 5px; } +.thirds { padding-top: 5px; padding-right: 5px; } +.thirditem, .fpitem, .fpcat, .fphead { + clear: both; + border-bottom: 1px solid #eee; + padding: 5px 0; +} +.fpcat { + padding: 0; +} +.thirditem, .fpitem, .fphead { padding-left: 7px; } +.fphead { + border-bottom: none; + border-right: 3px solid #ccc; + background-color: #eee; +} +.chevron::before { + border-style: solid; + border-width: 0.1em 0.1em 0 0; + content: ''; + display: inline-block; + height: 0.45em; + left: 0.15em; + position: relative; + vertical-align: top; + width: 0.45em; + float: right; + margin-right: 10px; + transform: rotate(-45deg); + top: 0.45em; +} +.chevron.uparrow::before { + transform: rotate(135deg); + top: 0.1em; +} +.fpoptions { display: none; } +.thirditem:hover { + background-color: #f0f0f0; +} +.fpitem:hover { + background-color: #f5f5f5; +} +.fphead:hover { + background-color: #ccc; +} +.wot { padding-right: 4px; } +.wot a { color: #555; text-decoration: none; } +#currentdomain .wot a { color: #fff; } +.wot a:hover { text-decoration: underline; } +.choices { float: right; } +.domainoutput { + max-width: 536px; + display: inline-block; +} +.domainname.domainoutput { + max-width: 531px; +} + +/* Footer */ +#bottom { padding-top: 10px; padding-bottom: 5px; } +#credit { text-align: right; padding-right: 5px; font-weight: bold; font-size: 0.92em; color: #555; } +#credit a, #pop_options { text-decoration: underline; } +#credit a:hover, #pop_options:hover { text-decoration: none; } +#closebtn { width: 5px; text-align: right; } + +/* Misc */ +hr { border: 0; border-top: 1px solid #eeeeee; } +.bolded, #pop_options, #pop_log { font-weight: bold; } + +/* Boxes */ +.box:hover, .fphead { + cursor: hand; + cursor: pointer; +} +.box { + cursor: default; + font-size: 0.92em; + font-weight: bold; + text-align: center; + min-width: 12%; + outline: 0; + padding: 8px 6px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + text-shadow: 0 1px rgba(0, 0, 0, 0.1); + margin: 2px; +} +span.box { + padding: 0 8px; + margin: 0; + height: 16px; + line-height: 16px; + margin-left: 2px; +} +.box:hover { + text-decoration: underline; +} +.selected { + border: 1px solid #000 !important; + text-decoration: line-through !important; + background-color: #999 !important; + background-image: none !important; +} +.box1, .allowed { + border: 1px solid #29691D; + color: white; + background-color: #3D9400; + background-image: -webkit-linear-gradient(top,#3d9400,#398a00); + background-image: -moz-linear-gradient(top,#3d9400,#398a00); + background-image: -ms-linear-gradient(top,#3d9400,#398a00); + background-image: -o-linear-gradient(top,#3d9400,#398a00); + background-image: linear-gradient(top,#3d9400,#398a00); +} +.box1:active { + -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -ms-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -o-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); +} +.box1:focus { + -moz-box-shadow: inset 0 0 0 1px #fff; + -ms-box-shadow: inset 0 0 0 1px #fff; + -o-box-shadow: inset 0 0 0 1px #fff; + -webkit-box-shadow: inset 0 0 0 1px #fff; + box-shadow: inset 0 0 0 1px #fff; + outline: 1px solid #3D9400; + outline: 0 transparent; + border: 1px solid white; + border: 1px solid transparent; +} +.box1:hover { + border: 1px solid #2D6200; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + background-color: #368200; + background-image: -webkit-linear-gradient(top,#3d9400,#368200); + background-image: -moz-linear-gradient(top,#3d9400,#368200); + background-image: -ms-linear-gradient(top,#3d9400,#368200); + background-image: -o-linear-gradient(top,#3d9400,#368200); + background-image: linear-gradient(top,#3d9400,#368200); + -moz-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -ms-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -o-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + box-shadow: 0 1px 1px rgba(0,0,0,0.1); +} +.box2, .blocked { + background-color: #a60000; + border: 1px solid #7a0000; + color: white; + background-image: -webkit-linear-gradient(top,#a60000,#990000); + background-image: -moz-linear-gradient(top,#a60000,#990000); + background-image: -ms-linear-gradient(top,#a60000,#990000); + background-image: -o-linear-gradient(top,#a60000,#990000); + background-image: linear-gradient(top,#a60000,#990000); +} +.box2:active { + -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -ms-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -o-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); +} +.box2:focus { + -moz-box-shadow: inset 0 0 0 1px #fff; + -ms-box-shadow: inset 0 0 0 1px #fff; + -o-box-shadow: inset 0 0 0 1px #fff; + -webkit-box-shadow: inset 0 0 0 1px #fff; + box-shadow: inset 0 0 0 1px #fff; + outline: 1px solid #a60000; + outline: 0 transparent; + border: 1px solid white; + border: 1px solid transparent; +} +.box2:hover { + border: 1px solid #7a0000; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + background-color: #940000; + background-image: -webkit-linear-gradient(top,#a60000,#940000); + background-image: -moz-linear-gradient(top,#a60000,#940000); + background-image: -ms-linear-gradient(top,#a60000,#940000); + background-image: -o-linear-gradient(top,#a60000,#940000); + background-image: linear-gradient(top,#a60000,#940000); + -moz-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -ms-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -o-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + box-shadow: 0 1px 1px rgba(0,0,0,0.1); +} +.box3 { + background-color: #4D90FE; + border: 1px solid #3079ED; + color: white; + background-image: -webkit-linear-gradient(top,#4d90fe,#4787ed); + background-image: -moz-linear-gradient(top,#4d90fe,#4787ed); + background-image: -ms-linear-gradient(top,#4d90fe,#4787ed); + background-image: -o-linear-gradient(top,#4d90fe,#4787ed); + background-image: linear-gradient(top,#4d90fe,#4787ed); +} +.box3:active { + -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -ms-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -o-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); +} +.box3:focus { + -moz-box-shadow: inset 0 0 0 1px #fff; + -ms-box-shadow: inset 0 0 0 1px #fff; + -o-box-shadow: inset 0 0 0 1px #fff; + -webkit-box-shadow: inset 0 0 0 1px #fff; + box-shadow: inset 0 0 0 1px #fff; + outline: 1px solid #4D90FE; + outline: 0 transparent; + border: 1px solid white; + border: 1px solid transparent; +} +.box3:hover { + border: 1px solid #2F5BB7; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + background-color: #357AE8; + background-image: -webkit-linear-gradient(top,#4D90FE,#357AE8); + background-image: -moz-linear-gradient(top,#4D90FE,#357AE8); + background-image: -ms-linear-gradient(top,#4D90FE,#357AE8); + background-image: -o-linear-gradient(top,#4D90FE,#357AE8); + background-image: linear-gradient(top,#4D90FE,#357AE8); + -moz-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -ms-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -o-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + box-shadow: 0 1px 1px rgba(0,0,0,0.1); +} +.box4 { + background-color: #444; + border: 1px solid #333333; + color: white; + background-image: -webkit-linear-gradient(top,#444,#222); + background-image: -moz-linear-gradient(top,#444,#222); + background-image: -ms-linear-gradient(top,#444,#222); + background-image: -o-linear-gradient(top,#444,#222); + background-image: linear-gradient(top,#444,#222); +} +.box4:active { + -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -ms-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -o-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); +} +.box4:focus { + -moz-box-shadow: inset 0 0 0 1px #000; + -ms-box-shadow: inset 0 0 0 1px #000; + -o-box-shadow: inset 0 0 0 1px #000; + -webkit-box-shadow: inset 0 0 0 1px #000; + box-shadow: inset 0 0 0 1px #000; + outline: 1px solid #EFEFEF; + outline: 0 transparent; + border: 1px solid black; + border: 1px solid transparent; +} +.box4:hover { + border: 1px solid #222222; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + background-color: #444; + background-image: -webkit-linear-gradient(top,#222,#444); + background-image: -moz-linear-gradient(top,#222,#444); + background-image: -ms-linear-gradient(top,#222,#444); + background-image: -o-linear-gradient(top,#222,#444); + background-image: linear-gradient(top,#222,#444); + -moz-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -ms-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -o-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + box-shadow: 0 1px 1px rgba(0,0,0,0.1); +} +.box5, .domainname { + clear: both; + width: auto; + margin-top: 5px; + background-color: #444; + border: 1px solid #333333; + color: white; + background-image: -webkit-linear-gradient(top,#444,#222); + background-image: -moz-linear-gradient(top,#444,#222); + background-image: -ms-linear-gradient(top,#444,#222); + background-image: -o-linear-gradient(top,#444,#222); + background-image: linear-gradient(top,#444,#222); +} +.box5:active { + -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -ms-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -o-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); +} +.box5:focus { + -moz-box-shadow: inset 0 0 0 1px #000; + -ms-box-shadow: inset 0 0 0 1px #000; + -o-box-shadow: inset 0 0 0 1px #000; + -webkit-box-shadow: inset 0 0 0 1px #000; + box-shadow: inset 0 0 0 1px #000; + outline: 1px solid #EFEFEF; + outline: 0 transparent; + border: 1px solid black; + border: 1px solid transparent; +} +.box5:hover { + border: 1px solid #222222; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + background-color: #444; + background-image: -webkit-linear-gradient(top,#222,#444); + background-image: -moz-linear-gradient(top,#222,#444); + background-image: -ms-linear-gradient(top,#222,#444); + background-image: -o-linear-gradient(top,#222,#444); + background-image: linear-gradient(top,#222,#444); + -moz-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -ms-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -o-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + box-shadow: 0 1px 1px rgba(0,0,0,0.1); +} + +/* Temp. Permissions */ +.allowsession, .prevoke, .clearglobaltemp { + clear: both; + margin-left: 0px; + margin-right: 0px; + margin-bottom: 0px; +} \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/recents.css b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/recents.css new file mode 100644 index 0000000..6d81260 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/css/recents.css @@ -0,0 +1,270 @@ +/* Global */ +body { padding: 25px 0; } +.table { font-size: 0.9em; } +#title, .i18_support { color: green; font-size: 30px; font-weight: bold; } +#title:hover { color: blue; } +#message { z-index: 999; color: #000; margin: 5px; position: fixed; top: 0; right: 0; padding: 5px; font-weight: bold; } +#message { display: none; } +li.active { font-weight: bold; } + +/* Boxes */ +.box:hover, .fphead { + cursor: hand; + cursor: pointer; +} +.box { + cursor: default; + font-size: 0.92em; + font-weight: bold; + text-align: center; + min-width: 12%; + outline: 0; + padding: 8px 6px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + text-shadow: 0 1px rgba(0, 0, 0, 0.1); + margin: 2px; +} +span.box { + padding: 0 8px; + margin: 0; + height: 16px; + line-height: 16px; + margin-left: 2px; +} +.box:hover { + text-decoration: underline; +} +.selected { + border: 1px solid #000 !important; + text-decoration: line-through !important; + background-color: #999 !important; + background-image: none !important; +} +.box1, .allowed { + border: 1px solid #29691D; + color: white; + background-color: #3D9400; + background-image: -webkit-linear-gradient(top,#3d9400,#398a00); + background-image: -moz-linear-gradient(top,#3d9400,#398a00); + background-image: -ms-linear-gradient(top,#3d9400,#398a00); + background-image: -o-linear-gradient(top,#3d9400,#398a00); + background-image: linear-gradient(top,#3d9400,#398a00); +} +.box1:active { + -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -ms-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -o-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); +} +.box1:focus { + -moz-box-shadow: inset 0 0 0 1px #fff; + -ms-box-shadow: inset 0 0 0 1px #fff; + -o-box-shadow: inset 0 0 0 1px #fff; + -webkit-box-shadow: inset 0 0 0 1px #fff; + box-shadow: inset 0 0 0 1px #fff; + outline: 1px solid #3D9400; + outline: 0 transparent; + border: 1px solid white; + border: 1px solid transparent; +} +.box1:hover { + border: 1px solid #2D6200; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + background-color: #368200; + background-image: -webkit-linear-gradient(top,#3d9400,#368200); + background-image: -moz-linear-gradient(top,#3d9400,#368200); + background-image: -ms-linear-gradient(top,#3d9400,#368200); + background-image: -o-linear-gradient(top,#3d9400,#368200); + background-image: linear-gradient(top,#3d9400,#368200); + -moz-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -ms-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -o-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + box-shadow: 0 1px 1px rgba(0,0,0,0.1); +} +.box2, .blocked { + background-color: #a60000; + border: 1px solid #7a0000; + color: white; + background-image: -webkit-linear-gradient(top,#a60000,#990000); + background-image: -moz-linear-gradient(top,#a60000,#990000); + background-image: -ms-linear-gradient(top,#a60000,#990000); + background-image: -o-linear-gradient(top,#a60000,#990000); + background-image: linear-gradient(top,#a60000,#990000); +} +.box2:active { + -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -ms-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -o-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); +} +.box2:focus { + -moz-box-shadow: inset 0 0 0 1px #fff; + -ms-box-shadow: inset 0 0 0 1px #fff; + -o-box-shadow: inset 0 0 0 1px #fff; + -webkit-box-shadow: inset 0 0 0 1px #fff; + box-shadow: inset 0 0 0 1px #fff; + outline: 1px solid #a60000; + outline: 0 transparent; + border: 1px solid white; + border: 1px solid transparent; +} +.box2:hover { + border: 1px solid #7a0000; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + background-color: #940000; + background-image: -webkit-linear-gradient(top,#a60000,#940000); + background-image: -moz-linear-gradient(top,#a60000,#940000); + background-image: -ms-linear-gradient(top,#a60000,#940000); + background-image: -o-linear-gradient(top,#a60000,#940000); + background-image: linear-gradient(top,#a60000,#940000); + -moz-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -ms-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -o-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + box-shadow: 0 1px 1px rgba(0,0,0,0.1); +} +.box3 { + background-color: #4D90FE; + border: 1px solid #3079ED; + color: white; + background-image: -webkit-linear-gradient(top,#4d90fe,#4787ed); + background-image: -moz-linear-gradient(top,#4d90fe,#4787ed); + background-image: -ms-linear-gradient(top,#4d90fe,#4787ed); + background-image: -o-linear-gradient(top,#4d90fe,#4787ed); + background-image: linear-gradient(top,#4d90fe,#4787ed); +} +.box3:active { + -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -ms-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -o-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); +} +.box3:focus { + -moz-box-shadow: inset 0 0 0 1px #fff; + -ms-box-shadow: inset 0 0 0 1px #fff; + -o-box-shadow: inset 0 0 0 1px #fff; + -webkit-box-shadow: inset 0 0 0 1px #fff; + box-shadow: inset 0 0 0 1px #fff; + outline: 1px solid #4D90FE; + outline: 0 transparent; + border: 1px solid white; + border: 1px solid transparent; +} +.box3:hover { + border: 1px solid #2F5BB7; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + background-color: #357AE8; + background-image: -webkit-linear-gradient(top,#4D90FE,#357AE8); + background-image: -moz-linear-gradient(top,#4D90FE,#357AE8); + background-image: -ms-linear-gradient(top,#4D90FE,#357AE8); + background-image: -o-linear-gradient(top,#4D90FE,#357AE8); + background-image: linear-gradient(top,#4D90FE,#357AE8); + -moz-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -ms-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -o-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + box-shadow: 0 1px 1px rgba(0,0,0,0.1); +} +.box4 { + background-color: #444; + border: 1px solid #333333; + color: white; + background-image: -webkit-linear-gradient(top,#444,#222); + background-image: -moz-linear-gradient(top,#444,#222); + background-image: -ms-linear-gradient(top,#444,#222); + background-image: -o-linear-gradient(top,#444,#222); + background-image: linear-gradient(top,#444,#222); +} +.box4:active { + -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -ms-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -o-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); +} +.box4:focus { + -moz-box-shadow: inset 0 0 0 1px #000; + -ms-box-shadow: inset 0 0 0 1px #000; + -o-box-shadow: inset 0 0 0 1px #000; + -webkit-box-shadow: inset 0 0 0 1px #000; + box-shadow: inset 0 0 0 1px #000; + outline: 1px solid #EFEFEF; + outline: 0 transparent; + border: 1px solid black; + border: 1px solid transparent; +} +.box4:hover { + border: 1px solid #222222; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + background-color: #444; + background-image: -webkit-linear-gradient(top,#222,#444); + background-image: -moz-linear-gradient(top,#222,#444); + background-image: -ms-linear-gradient(top,#222,#444); + background-image: -o-linear-gradient(top,#222,#444); + background-image: linear-gradient(top,#222,#444); + -moz-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -ms-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -o-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + box-shadow: 0 1px 1px rgba(0,0,0,0.1); +} +.box5, .domainname { + clear: both; + width: auto; + margin-top: 5px; + background-color: #444; + border: 1px solid #333333; + color: white; + background-image: -webkit-linear-gradient(top,#444,#222); + background-image: -moz-linear-gradient(top,#444,#222); + background-image: -ms-linear-gradient(top,#444,#222); + background-image: -o-linear-gradient(top,#444,#222); + background-image: linear-gradient(top,#444,#222); +} +.box5:active { + -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -ms-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -o-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); + box-shadow: inset 0 1px 2px rgba(0,0,0,0.3); +} +.box5:focus { + -moz-box-shadow: inset 0 0 0 1px #000; + -ms-box-shadow: inset 0 0 0 1px #000; + -o-box-shadow: inset 0 0 0 1px #000; + -webkit-box-shadow: inset 0 0 0 1px #000; + box-shadow: inset 0 0 0 1px #000; + outline: 1px solid #EFEFEF; + outline: 0 transparent; + border: 1px solid black; + border: 1px solid transparent; +} +.box5:hover { + border: 1px solid #222222; + text-shadow: 0 1px rgba(0, 0, 0, 0.3); + background-color: #444; + background-image: -webkit-linear-gradient(top,#222,#444); + background-image: -moz-linear-gradient(top,#222,#444); + background-image: -ms-linear-gradient(top,#222,#444); + background-image: -o-linear-gradient(top,#222,#444); + background-image: linear-gradient(top,#222,#444); + -moz-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -ms-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -o-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + box-shadow: 0 1px 1px rgba(0,0,0,0.1); +} + +/* Temp. Permissions */ +.allowsession, .prevoke, .clearglobaltemp { + clear: both; + margin-left: 0px; + margin-right: 0px; + margin-bottom: 0px; +} \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/fonts/glyphicons-halflings-regular.eot b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000000000000000000000000000000000000..b93a4953fff68df523aa7656497ee339d6026d64 GIT binary patch literal 20127 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/fonts/glyphicons-halflings-regular.ttf b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1413fc609ab6f21774de0cb7e01360095584f65b GIT binary patch literal 45404 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 G`u_lkUM?>H literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/fonts/glyphicons-halflings-regular.woff2 b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..64539b54c3751a6d9adb44c8e3a45ba5a73b77f0 GIT binary patch literal 18028 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 literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/gpl.txt b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/gpl.txt new file mode 100644 index 0000000..abf09ee --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/gpl.txt @@ -0,0 +1,619 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/background.html b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/background.html new file mode 100644 index 0000000..52c9dfb --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/background.html @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/options.html b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/options.html new file mode 100644 index 0000000..a84576a --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/options.html @@ -0,0 +1,296 @@ + + + + + + + ScriptSafe Options + + + + + + +
+
+
+ +
+
+ + + +

+ To support development, click the heart :)
Bitcoin: 39VJ5L9Yd6WocG6r88uE7ZZnM5J2M5bW92 +

+ +
+
+
+
+ +
+

General Settings

+
+
+ + + + + + + + + + + +
(Default: enabled)
(Default: disabled)

(Default: Block)
+
+
+
+
+
+
+
+
+
+
+ +
(Default: Control Cross-Domain Requests (allow Same-Domain); control XML HTTP Requests)
(Default: enabled; show popup when settings synced to your Google Account)
(Default: enabled; show popup when settings synced from your Google Account)
(Default: enabled; show changelog page when ScriptSafe is updated)
(Default: enabled)

Available hotkey actions:

  • Temporarily allow/block all resources for a current tab
  • Remove temporary permissions for a current tab
  • Remove all temporary permissions

Configure ScriptSafe hotkeys (click on "Keyboard Shortcuts")

+
+
+ + + + + + + + + + + + + + +

(Default: disabled; prevent sites from reading your browser plugin details)

Whitelist ():

Domain:
 

(Default: Disabled; protect against fingerprinting attempts through <canvas> elements)

Whitelist ():

Domain:
 

(Default: disabled; prevent fingerprinting via the AudioContext API)

Whitelist ():

Domain:
 

(Default: disabled; prevent fingerprinting via the WebGL API)

Whitelist ():

Domain:
 

(Default: disabled; prevent fingerprinting via the Battery API)

Whitelist ():

Domain:
 

(Default: disabled; prevent having hardware devices detected via the WebRTC API)

Whitelist ():

Domain:
 

(Default: disabled; prevent having devices detected via the Gamepad API)

Whitelist ():

Domain:
 

(Default: disabled; prevent having devices detected via the WebVR API)

Whitelist ():

Domain:
 

(Default: disabled; prevent having devices detected via the Bluetooth API)

Whitelist ():

Domain:
 

(Default: disabled; prevent system fonts from being enumerated through <canvas> elements. May interfere with Google Docs.)

Whitelist ():

Domain:
 

(Default: disabled; prevent fingerprinting through calculating element rectangles. May interfere with some dropdowns.)

Whitelist ():

Domain:
 

(Default: disabled; prevent pages from interfering with clipboard actions)

Whitelist ():

Domain:
 

(Default: disabled; make keypress timings more random to increase anonymity (NOTE: adds a random delay between keypresses; disable this setting if unacceptable))

ms (Default: 40ms)
+
+
+ + + + + + + + + + + + + + +
(Default: disabled; block allowed domains on unlisted domains)
(Default: enabled; remove unwanted content from known ad / malware domains; domains gathered from MVPS HOSTS, hpHOSTS (ad / tracking servers), Peter Lowe's HOSTS Project, MalwareDomainList.com)
(Default: enabled; blocks cookies from known ad / malware domains; below mode applies to this as well)
(Default: Relaxed; Relaxed = whitelisted domains will not be blocked; Strict = domains in the unwanted domain list will be blocked even if whitelisted)
(Default: disabled; always remove social widgets/buttons, even if whitelisted)
For more comprehensive blocking, check out Privacy Badger, Disconnect, Blur, and/or uBlock Origin with all of the subscription lists on the Fanboy site)
(Default: enabled; remove "invisible" third-party elements)
(Default: disabled; remove Google Analytics (UTM) tracking tokens)
(Default: disabled; remove possible tracking tokens passed using hash, where there is an attribute and value (e.g. #xtor=RSS-1))

(Default: Protect Local IP; prevent IP address leakage)
(Default: Only on Unwhitelisted Domains; blocks referrer information when clicking on third-party links (note: setting this to "On All Domains" may cause issues (e.g. thumbnails in Tweetdeck))
(Default: disabled; spoof or randomize your timezone. NOTE: if enabled, it may interfere with replying to emails in Gmail.)
(Default: -Off-; spoofs your user-agent (browser and OS)) +

useragentstring.com | whatismybrowser.com

+

+

(Default: -Off-; warning: if enabled, may "break" some sites (e.g. logging in))

+

+
+
+ + + + + + + +
(Default: -Off-; modifies how all links are opened)
(Default: Disabled; preserve same-domain elements)
(Default: enabled; auto-refresh page after list change)
(Default: enabled; if ticked, adds rating button under domains in tab popup)
(Default: disabled; if ticked, closes tab options everytime an option is clicked)
(Default: enabled; sorts URL lists by domains on this page and in the panel)
+
+
+ + + + +

  • Entire Domain Matching: match an entire domain by adding **. in front of it (or click on "Trust"/"Distrust" for an existing entry below).
    Example: **.domain.com will match domain.com, a.domain.com, 1.2.domain.com and even 1.2.3.domain.com
  • Wildcard Matching: match any string of characters (except periods ".") with a * character.
    Example: *.domain.com will match a.domain.com but not domain.com (no subdomain)
  • Single-Character Matching: match any single character with a ? character.
    Example: cat?.com will match cats.com and cat5.com

  • Note 1: you are able to combine the various matching expressions (e.g. 192.16?.*.*, *.cat?.c*, abc*xyz.com)
  • Note 2: IPv6 URLs must contain square brackets (e.g. [2001:4860:0:2001::68])
  • Note 3: the "www." prefix is automatically handled, so all entries below have it stripped out

Whitelist () (clear | bulk import)


Tip: press CTRL+F to search the lists

Blacklist () (clear | bulk import)

+
Bulk Import

Copy and paste domains into the box below. Each domain should be on a separate line.

+
+
+ + + +
ImportExport (select all)

+


+
+
+
+ Translations: Chinese - Simplified (Chiuwing LUK), Chinese - Traditional (Sam Lee), Czech (callipso), Dutch (Robert J. Klop), French (Marc0303), German (Daniel Neubauer (d4nin3u), Daniel Ring), Hungarian (Calmarius), Italian (Ezio Tescari), Japanese (noushibou, たこすけ), Korean (ARMO), Latvian (Hudozhnik), Polish (Galileusz), Romanian (Sirius98), Russian (WatsonRus), Spanish (Enrique Arróniz Ramos), Swedish (Guy Fredlund) +
+
+
+ + + + + \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/popup.html b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/popup.html new file mode 100644 index 0000000..0b6208a --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/popup.html @@ -0,0 +1,24 @@ + + + + + + + + + + + +
+ + + + + +
+ +
+ + \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/recents.html b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/recents.html new file mode 100644 index 0000000..fd776e5 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/recents.html @@ -0,0 +1,54 @@ + + + + + + + ScriptSafe Log + + + + + + +
+
+
+
+ ScriptSafe +
+
+

+
+
+
+ +
+
+
+
+
+
TimeItemTypeTab URLOptions
+
+
+
+
+
+
+
+
+
TimeItemTypeTab URLOptions
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/updated.html b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/updated.html new file mode 100644 index 0000000..709ca99 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/html/updated.html @@ -0,0 +1,85 @@ + + + + + + + ScriptSafe Updated! + + + + + +
+
+
+
+ ScriptSafe
by Andrew Y.

+ ScriptSafe: Chrome / Firefox / Opera | Github | Website | Beta Testing | Submit Issues
+ Changelog (Version History) | Help Translate! +
+
+
+ + + +

+ To support development, click the heart :)
Bitcoin: 39VJ5L9Yd6WocG6r88uE7ZZnM5J2M5bW92 +

+ Options +
+
+
+
+

Updated to v! (Tuesday, December 12, 2017)

+
+

ScriptSafe is on Chrome, Opera, and now: Firefox!

My sincere thanks to all testers, translators, and users for your support over the past 6 years.

+

In this release you will find the following updates:

+
    +
  • v1.0.9.3:
      +
    • Added ability to temporarily disable ScriptSafe for a set time via the panel (useful if buying something online)
    • +
    • Added ability to selectively allow Browser Plugins Enumeration
    • +
    • Added ability to randomize user agents: on every request or every x minutes
    • +
    • Minor panel tweaks
    • +
    • Minor user agent fix
    • +
    • Updated unwanted content providers list
    • +
  • +
+

I have put together some documentation for ScriptSafe, including "Getting Started" instructions.

+

If you run into any issues, please create an issue in Github.

+

I am quite active on Twitter, so if you don't mind the occasional cat tweet, you are free to follow me: @andryou.

+

Thank you,
+ -Andrew

+
+
+
+

+ + +

+

+ +

+
+
+

Just as a note: ScriptSafe does not have any backdoors, nor does it contact any third-party server at any point in time. ScriptSafe was created by one ordinary guy (me), lovingly coded in my bedroom, without any intention other than to give you back control of the Internet. ScriptSafe is released under the GNU GPLv3 license for true open-source freedom.

You can go through the code on Github (https://github.com/andryou/scriptsafe) AND the actual instance installed on your computer by going to C:\Users\**your username**\AppData\Local\Google\Chrome\User Data\Default\Extensions\oiigbmnaadbkfbmpbfijlflahbdbdgdf (for non-Windows users, search for a folder named oiigbmnaadbkfbmpbfijlflahbdbdgdf)

+
+ +
+ Translations: Chinese - Simplified (Chiuwing LUK), Chinese - Traditional (Sam Lee), Czech (callipso), Dutch (Robert J. Klop), French (Marc0303), German (Daniel Neubauer (d4nin3u), Daniel Ring), Hungarian (Calmarius), Italian (Ezio Tescari), Japanese (noushibou, たこすけ), Korean (ARMO), Latvian (Hudozhnik), Polish (Galileusz), Romanian (Sirius98), Russian (WatsonRus), Spanish (Enrique Arróniz Ramos), Swedish (Guy Fredlund) +
+ + + + + \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/IconAllowed.png b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/IconAllowed.png new file mode 100644 index 0000000000000000000000000000000000000000..eaf0b7624f0092634e44d71e83d198c4830a576f GIT binary patch literal 3529 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 Do)@ri literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/IconDisabled.png b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/IconDisabled.png new file mode 100644 index 0000000000000000000000000000000000000000..573f103f735c67567b890d8dcb99e3841e117e89 GIT binary patch literal 3388 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 R%838~002ovPDHLkV1j^IbYB1f literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/heartbig.png b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/heartbig.png new file mode 100644 index 0000000000000000000000000000000000000000..6324406400c1ecd179142f387cbb6a412a4bf70a GIT binary patch literal 4899 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 VXVs55J%<1Q002ovPDHLkV1j?(I4%GH literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/heartsmall.png b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/heartsmall.png new file mode 100644 index 0000000000000000000000000000000000000000..c8c06bf08c3cbcb96afa3688359edcc5ec0f551f GIT binary patch literal 3661 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 literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/icon128.png b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..361e2f8a3e2cef81f6301efb92b5d469e037e11e GIT binary patch literal 5903 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 literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/icon16.png b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/icon16.png new file mode 100644 index 0000000000000000000000000000000000000000..1bc908c8971745da56436c4e8ac6ce3cb49c0ba8 GIT binary patch literal 809 zcmV+^1J?YBP)KT@~aoK9~?mgFx#eXubyol)p$_x}Hf*B~nBGlA2zhY=WYi#Ha z>*_5U8%o)>wUYKThe#xolou}M#;;NOZ^zL!AJ>&MDvefc;K{JIxwaA4p3dM%oGE$Z zWEi7tKe3168HMz4NKoJM@OOiN*CFQm7Hkp=Djz! zP>`DsK*g+Tj{f*1WhEw#qY(%M0-n#9elzFphgGGE%y`maZS;NiH0@&M)B;{cWJ-wZ zw}yy4x=NvC;iQBh0D+_yEzTjHv}s-Q4(pfJ1F*NdoufZ=rj_j9yq6pOI<{-$2jEVu zp*>A$tgC8BGcPEP(slmpG_Al2(_Foc-})bbrtmz$wgoL@Bxw0!N1FMqnntp+3_5@A zO4C;@T+QDDE`s3s0^*{!_EZk{e1C6R|B>sI%qW^PKOTERBqt95JLR&k=QDbLIZbs{ zi2R6&?*Vut8W=?98GLZ^qx8c~gg@>%LQ%_n&i?T^E9$~nxhA$PxV|v%3tA$PB3d?! zzCX_q8@NANaQM69OnZAC$1d%st~Q7IC9e|rnIy*pJYVoU0b5X;I+i@l%*MJuJc1El zMAPf*InaBE;p81^7SCb!%v@Y&BJfXmJW?BuRga*n{OoMSt=L2UiaqAFxl<{d8zBh5 zasMSaSsP4ESgguqZx5y6E;Lx_=Frcgg0hOMgXNpd>4?L zv?!aZGyB2Y587ME&dTN2mAe2q{984J$ENetgh)C&6p(jUyM}?N{$NsNf+nS$1+4h4 zau4hFu4UQc6f!4;(dP80^2iZF!yY4fXd;oJHhw&M29JOmg8Y2xa!O1ALb@hWv1QG( zp-3rD0%I2yujHCu%=Aga=-$MF`oi z3)3_kw57|IeqzzdIt1ykUfrbVcm_iX9*qruCm%Q0{yiw5*J(q6(%a zXZNzdI`1QPS2Uuz2~1PEWS1#X$nj`8`0>8rj1lPo40|IUe;+U2%Fbs_W={Xl1W127 zmypNLlNcLLYr8;IA9zWEX2oZ2Skq8jY;fT#`D{J3gUpeWc|K$2Lxu0+_=R(1=V!3! zr8t~U1tI#BG^-iO1G#d&j;fk!HZ3UZBmT{y9elCxYnL0O#w9Q=ata4eUB}&B#Wefg zgOc`PSkv63HNme<&vPkaooz#Jzk}TCa5;az<}#y~q_QAyC~k^|ai0srpjqu~_ocYJ zq~92>Z90par!N)Nhh5H(jEd#ygc%$-)_`WwdYxVfP-9|zIseCTemL>7OM$_`gIG9q zK3T74(A3iGvS$5))%;cOpi4IpLf#|Ni8{#UNmQJT#hX|3>ta^QGmILOhQrY}rdw@x z7H)WtkN_`GdVdoFP&?WM>ElAF`}-nW_I>MegNgAeoGYp%V&LF=V|O}QyM^Zt=R|Ee zlheX*=(78w?^1yBTuE}1zn6P(<;h0uO@7pURn@N(fOba*1v@`w)z0;3ZVo1=L=Y3{ zgRTplM(L@-%D&R-`cPE%I#|j&5Ng?bT#7=gUFjXoFNfDw6zHw zdfz81bx`IMCpBTQ1bcd@T)uXj3l}7}w_WNbTv)b&{OyIzWZJZNQpXNJQK79>pgTd_ zpZS9Xl$o27?4bEe!0N7Gx9c1@+=#b_7xr6?1cn5Y5En>TxEoR`IGq9^AKJ|B$1RHx zN%pF literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/icon32.png b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/img/icon32.png new file mode 100644 index 0000000000000000000000000000000000000000..ad84c20fffec9ee8335796b7c933466e0011637c GIT binary patch literal 1814 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 kZmD*ylh07*qoM6N<$f2)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.6",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.6",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.6",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.6",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.6",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.6",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.6",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.6",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/common.js b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/common.js new file mode 100644 index 0000000..d34fd6d --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/common.js @@ -0,0 +1,113 @@ +// ScriptSafe - Copyright (C) andryou +// Distributed under the terms of the GNU General Public License +// The GNU General Public License can be found in the gpl.txt file. Alternatively, see . +'use strict'; +function baddies(src, amode, antisocial, lookupmode) { + lookupmode = lookupmode || 1; + var dmn = extractDomainFromURL(src); + var topDomain = getDomain(dmn); + if (dmn.indexOf(".") == -1 && src.indexOf(".") != -1) dmn = src; + if (antisocial == 'true' && (antisocial2.indexOf(dmn) != -1 || antisocial1.indexOf(topDomain) != -1 || src.indexOf("digg.com/tools/diggthis.js") != -1 || src.indexOf("/googleapis.client__plusone.js") != -1 || src.indexOf("apis.google.com/js/plusone.js") != -1 || src.indexOf(".facebook.com/connect") != -1 || src.indexOf(".facebook.com/plugins") != -1 || src.indexOf(".facebook.com/widgets") != -1 || src.indexOf(".fbcdn.net/connect.php/js") != -1 || src.indexOf(".stumbleupon.com/hostedbadge") != -1 || src.indexOf(".youtube.com/subscribe_widget") != -1 || src.indexOf(".ytimg.com/yt/jsbin/www-subscribe-widget") != -1 || src.indexOf("apis.google.com/js/platform.js") != -1 || src.indexOf("plus.google.com/js/client:plusone.js") != -1 || src.indexOf("linkedin.com/countserv/count/share") != -1)) + return '2'; + if ((amode == 'relaxed' && domainCheck(dmn, lookupmode) != '0') || amode == 'strict') { + if (binarySearch(yoyo1, topDomain) != -1) return '1'; + if (binarySearch(yoyo2, dmn) != -1) return '1'; + } + return false; +} +function thirdParty(url, taburl) { + if (url) { + var url = extractDomainFromURL(url); + var documentHost; + if (taburl === undefined) documentHost = window.location.hostname; + else documentHost = taburl; + url = url.replace(/\.+$/, ""); + documentHost = documentHost.replace(/\.+$/, ""); + if (url == documentHost) return false; // if they match exactly (same domain), our job here is done + // handle IP addresses (if we're still here, then it means the ip addresses don't match) + if (url.match(/^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})$/g) || documentHost.match(/^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})$/g) || url.match(/^(?:\[[A-Fa-f0-9:.]+\])(:[0-9]+)?$/g) || documentHost.match(/^(?:\[[A-Fa-f0-9:.]+\])(:[0-9]+)?$/g)) return true; + // now that IP addresses have been processed, carry on. + var elConst = url.split('.').reverse(); // work backwards :) + var pageConst = documentHost.split('.').reverse(); + var max = elConst.length; + if (max < pageConst.length) + max = pageConst.length; + var matchCount = 0; + for (var i=0;i 2) return false; + else if (matchCount == 2 && ((pageConst[1] == 'co' || pageConst[1] == 'com' || pageConst[1] == 'net') && pageConst[0] != 'com')) return true; + if (matchCount == 2) return false; + return true; + } + return false; // doesn't have a URL +} +function extractDomainFromURL(url) { // credit: NotScripts + if (!url) return ""; + if (url.indexOf("://") != -1) url = url.substr(url.indexOf("://") + 3); + if (url.indexOf("/") != -1) url = url.substr(0, url.indexOf("/")); + if (url.indexOf("@") != -1) url = url.substr(url.indexOf("@") + 1); + if (url.match(/^(?:\[[A-Fa-f0-9:.]+\])(:[0-9]+)?$/g)) { + if (url.indexOf("]:") != -1) return url.substr(0, url.indexOf("]:")+1); + return url; + } + if (url.indexOf(":") > 0) url = url.substr(0, url.indexOf(":")); + return url; +} +function getDomain(url, type) { + if (url && !url.match(/^((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})$/g) && !url.match(/^(?:\[[A-Fa-f0-9:.]+\])(:[0-9]+)?$/g) && url.indexOf(".") != -1) { + if (url[0] == '*' && url[1] == '*' && url[2] == '.') return url.substr(3); + url = url.split(".").reverse(); + var domain; + var len = url.length; + if (len > 1) { + if (type === undefined) domain = url[1]+'.'+url[0]; + else domain = url[1]; + if ((url[1] == 'co' || url[1] == 'com' || url[1] == 'net') && url[0] != 'com' && len > 2) { + if (type === undefined) domain = url[2]+'.'+url[1]+'.'+url[0]; + else domain = url[2]; + } + } + return domain; + } + return url; +} +function in_array(needle, haystack) { + if (!haystack || !needle) return false; + if (needle.indexOf('www.') == 0) needle = needle.substring(4); + if (binarySearch(haystack, needle) != -1) return '1'; + for (var i in haystack) { + if (haystack[i].indexOf("*") == -1 && haystack[i].indexOf("?") == -1) continue; + if (new RegExp('^(?:'+haystack[i].replace(/\./g, '\\.').replace(/^\[/, '\\[').replace(/\]$/, '\\]').replace(/\?/g, '.').replace(/^\*\*\\./, '(?:.+\\.|^)').replace(/\*/g, '[^.]+')+')$').test(needle)) return '1'; + } + return false; +} +// https://github.com/Olical/binary-search/blob/master/src/binarySearch.js +function binarySearch(list, item) { + var min = 0; + var max = list.length - 1; + var guess; + var bitwise = (max <= 2147483647) ? true : false; + if (bitwise) { + while (min <= max) { + guess = (min + max) >> 1; + if (list[guess] === item) { return guess; } + else { + if (list[guess] < item) { min = guess + 1; } + else { max = guess - 1; } + } + } + } else { + while (min <= max) { + guess = Math.floor((min + max) / 2); + if (list[guess] === item) { return guess; } + else { + if (list[guess] < item) { min = guess + 1; } + else { max = guess - 1; } + } + } + } + return -1; +} \ No newline at end of file diff --git a/src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/jquery.js b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/jquery.js new file mode 100644 index 0000000..1b4ad81 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/scriptsafe.js/js/jquery.js @@ -0,0 +1,4 @@ +/*! jQuery v2.2.4 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){var b;if("object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype||{},"isPrototypeOf"))return!1;for(b in a);return void 0===b||k.call(a,b)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return h.call(b,a)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&f.parentNode&&(this.length=1,this[0]=f),this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?void 0!==c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?h.call(n(a),this[0]):h.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||n.uniqueSort(e),D.test(a)&&e.reverse()),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.removeEventListener("DOMContentLoaded",J),a.removeEventListener("load",J),n.ready()}n.ready.promise=function(b){return I||(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(n.ready):(d.addEventListener("DOMContentLoaded",J),a.addEventListener("load",J))),I.promise(b)},n.ready.promise();var K=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)K(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},L=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function M(){this.expando=n.expando+M.uid++}M.uid=1,M.prototype={register:function(a,b){var c=b||{};return a.nodeType?a[this.expando]=c:Object.defineProperty(a,this.expando,{value:c,writable:!0,configurable:!0}),a[this.expando]},cache:function(a){if(!L(a))return{};var b=a[this.expando];return b||(b={},L(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[b]=c;else for(d in b)e[d]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=a[this.expando];if(void 0!==f){if(void 0===b)this.register(a);else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in f?d=[b,e]:(d=e,d=d in f?[d]:d.match(G)||[])),c=d.length;while(c--)delete f[d[c]]}(void 0===b||n.isEmptyObject(f))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!n.isEmptyObject(b)}};var N=new M,O=new M,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/[A-Z]/g;function R(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?n.parseJSON(c):c; +}catch(e){}O.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length",""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|&#?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",o=0;while(f=m[o++])if(d&&n.inArray(f,d)>-1)e&&e.push(f);else if(j=n.contains(f.ownerDocument,f),g=_(l.appendChild(f),"script"),j&&aa(g),c){k=0;while(f=g[k++])Z.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var da=/^key/,ea=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,fa=/^([^.]*)(?:\.(.+)|)/;function ga(){return!0}function ha(){return!1}function ia(){try{return d.activeElement}catch(a){}}function ja(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ja(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ha;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return"undefined"!=typeof n&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(G)||[""],j=b.length;while(j--)h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.hasData(a)&&N.get(a);if(r&&(i=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&N.remove(a,"handle events")}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(N.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,la=/\s*$/g;function pa(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function qa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function ra(a){var b=na.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function sa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(N.hasData(a)&&(f=N.access(a),g=N.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}O.hasData(a)&&(h=O.access(a),i=n.extend({},h),O.set(b,i))}}function ta(a,b){var c=b.nodeName.toLowerCase();"input"===c&&X.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function ua(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&ma.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),ua(f,b,c,d)});if(o&&(e=ca(b,a[0].ownerDocument,!1,a,d),g=e.firstChild,1===e.childNodes.length&&(e=g),g||d)){for(h=n.map(_(e,"script"),qa),i=h.length;o>m;m++)j=e,m!==p&&(j=n.clone(j,!0,!0),i&&n.merge(h,_(j,"script"))),c.call(a[m],j,m);if(i)for(k=h[h.length-1].ownerDocument,n.map(h,ra),m=0;i>m;m++)j=h[m],Z.test(j.type||"")&&!N.access(j,"globalEval")&&n.contains(k,j)&&(j.src?n._evalUrl&&n._evalUrl(j.src):n.globalEval(j.textContent.replace(oa,"")))}return a}function va(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(_(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&aa(_(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(ka,"<$1>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=_(h),f=_(a),d=0,e=f.length;e>d;d++)ta(f[d],g[d]);if(b)if(c)for(f=f||_(a),g=g||_(h),d=0,e=f.length;e>d;d++)sa(f[d],g[d]);else sa(a,h);return g=_(h,"script"),g.length>0&&aa(g,!i&&_(a,"script")),h},cleanData:function(a){for(var b,c,d,e=n.event.special,f=0;void 0!==(c=a[f]);f++)if(L(c)){if(b=c[N.expando]){if(b.events)for(d in b.events)e[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);c[N.expando]=void 0}c[O.expando]&&(c[O.expando]=void 0)}}}),n.fn.extend({domManip:ua,detach:function(a){return va(this,a,!0)},remove:function(a){return va(this,a)},text:function(a){return K(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.appendChild(a)}})},prepend:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(_(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return K(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!la.test(a)&&!$[(Y.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(_(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return ua(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(_(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),f=e.length-1,h=0;f>=h;h++)c=h===f?this:this.clone(!0),n(e[h])[b](c),g.apply(d,c.get());return this.pushStack(d)}});var wa,xa={HTML:"block",BODY:"block"};function ya(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function za(a){var b=d,c=xa[a];return c||(c=ya(a,b),"none"!==c&&c||(wa=(wa||n(" + + + + + + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/devtools.html b/src/i2p.chromium.base.profile/extensions/ublock.js/devtools.html new file mode 100644 index 0000000..ba14c5d --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/devtools.html @@ -0,0 +1,56 @@ + + + + + +uBlock — Dev tools + + + + + + + + + + + + + + + + +
+

+ + + + + + +

+
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/document-blocked.html b/src/i2p.chromium.base.profile/extensions/ublock.js/document-blocked.html new file mode 100644 index 0000000..ae841b9 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/document-blocked.html @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + +
+

+ +
+ +
+

+

+ +
+ +
+ + +
+ +
+

+

+ + +

+
+ + + + + + + + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/dyna-rules.html b/src/i2p.chromium.base.profile/extensions/ublock.js/dyna-rules.html new file mode 100644 index 0000000..bc20532 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/dyna-rules.html @@ -0,0 +1,67 @@ + + + + + +uBlock — Dynamic filtering rules + + + + + + + + + + + + + + +
+
+

info-circle

+
+
+
+
+ + +
+
+
+ + + +
+
+
+
+
+ filter  double-angle-up +
+
+ + + + + + + + + + + + + + + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/img/cloud.png b/src/i2p.chromium.base.profile/extensions/ublock.js/img/cloud.png new file mode 100644 index 0000000000000000000000000000000000000000..6c78dde2e027afd5108223aab236ea93e2e52ca7 GIT binary patch literal 5017 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 literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/img/fontawesome/LICENSE.txt b/src/i2p.chromium.base.profile/extensions/ublock.js/img/fontawesome/LICENSE.txt new file mode 100644 index 0000000..2784b8c --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/img/fontawesome/LICENSE.txt @@ -0,0 +1,25 @@ +# [Font Awesome v4.7.0](http://fontawesome.io) +### The iconic font and CSS framework + +Font Awesome is a full suite of 675 pictographic icons for easy scalable vector graphics on websites, +created and maintained by [Dave Gandy](https://twitter.com/davegandy). +Stay up to date with the latest release and announcements on Twitter: +[@fontawesome](http://twitter.com/fontawesome). + +Get started at http://fontawesome.io! + +## License +- The Font Awesome font is licensed under the SIL OFL 1.1: + - http://scripts.sil.org/OFL +- Font Awesome CSS, LESS, and Sass files are licensed under the MIT License: + - https://opensource.org/licenses/mit-license.html +- The Font Awesome documentation is licensed under the CC BY 3.0 License: + - http://creativecommons.org/licenses/by/3.0/ +- Attribution is no longer required as of Font Awesome 3.0, but much appreciated: + - `Font Awesome by Dave Gandy - http://fontawesome.io` +- Full details: http://fontawesome.io/license/ + +## Author +- Email: dave@fontawesome.io +- Twitter: http://twitter.com/davegandy +- GitHub: https://github.com/davegandy diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/img/fontawesome/fontawesome-defs.svg b/src/i2p.chromium.base.profile/extensions/ublock.js/img/fontawesome/fontawesome-defs.svg new file mode 100644 index 0000000..75bc67f --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/img/fontawesome/fontawesome-defs.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/img/help16.png b/src/i2p.chromium.base.profile/extensions/ublock.js/img/help16.png new file mode 100644 index 0000000000000000000000000000000000000000..32a8b44fb3e92213853427f75c5f6f6e02474c64 GIT binary patch literal 215 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6=6Sj}hEy=N25uE(cH}8pCv~Q@ zChd$&rP-e!#lhjPj>*IcroBJ&a;BSlTI}=dOq~n|#kkx9yn;9!8m#Ly1Gse#+>w`B z-(jHUGNo<{ix|VkH=3!fll<0p{b*P_HCC|B;^HOa)7RKDmVKAbj^bOklD}YPgX&iS z|I0C!2@W2n;dz2WLB5}zrzOsgyqw_Z(XaZ``6=_8fVa!@{;v`H!ECTyNJ>Lyy$aB! N44$rjF6*2UngGB_PjvtQ literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/img/icon_128.png b/src/i2p.chromium.base.profile/extensions/ublock.js/img/icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..42f2cb44d67da844b3f358bbde2ac22eddccdbc7 GIT binary patch literal 3552 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 literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/img/icon_16.png b/src/i2p.chromium.base.profile/extensions/ublock.js/img/icon_16.png new file mode 100644 index 0000000000000000000000000000000000000000..d72530a5244cf40f6768ba0429847bcd6b74770c GIT binary patch literal 420 zcmV;V0bBlwP)n?UIZa#p1PFuJATexe!Nh|D2NX|Eo@}wRsK|YFSXe4ZQv)O8{{{vI zFb1(fVjww?8jxC$9*|y;ZM;1a5`RGoNx>jJAiW?1*t9BgUA_8$nvM<_|M>6$rwc%O;08?B z*PjfNSQQ!yHstlSYyY?B<>3qikRG@J%l-WmNsR@N9=HJqo14Xf3F8_GiRd~=56l2C z-jbQA36yw5P_wxBlTE3q+Q@kUj<@CJT1{40zlBTlWHq(Bo72B2N^(?z=kk{6@N!x5XURjJ%4z3IHcq=G&FQ*v)R7qIPPnk&6c_~fFwz80C*Ds zQmK@dVVLf6xvW1qIWgw*d0U}S7|Uj}let{3`!jNs>@37UglFP>=vr0D!9c5JDvUPj;;(-5Wqvvku@M#2WzaH8stX3nkb6ZZ-e3-ilWNbc_2pstpc=SwOUk7SY9eQSFcc-rfWke z|Mqx14RglE#^j2);Rs$f05A-5q~x5>=K%ntqobf{8Z=GA>2%8P1wnw%=R;>_Co~!j zd_EuM=H?(t5<;QSO;sRk$=gz^)qf~Ci=qgV$ppXO55M1E_b!=Cg5x*{f&f7f>OMY! zKtQnq0HCX@s}Z2LxA%7?=WsYIhgGN3$x#tS5#ewcEXy`rM4?dVMsWbpYPAOq|A>x` zj^18dTied%a-Ho=!2bUJ()|4VxY1~g*X4lGXiQiv7O~wGU^bh>Rq;9i0AN}6huRFk zYg4IIFq_Rk)|F)K!0YuscDYTcPft&qDo?FeTXehK-??0_hX#Y;&)4dVwzs$6dG_qt zSMhkj$`vj-R+9{!Ez`7dtMFW$Nj$mMdHXf(PUi^V=TK0ekf`3wvUa08&;R0ZI6k-A?Xk9Y$2G^|PbQNO0l;pz gKk|CLKee*XUzXvEN1n!T9{>OV07*qoM6N<$f>hTPGynhq literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/img/icon_32.png b/src/i2p.chromium.base.profile/extensions/ublock.js/img/icon_32.png new file mode 100644 index 0000000000000000000000000000000000000000..c7c93e39df5e6a062b3a94453072713474e67a71 GIT binary patch literal 1278 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 N002ovPDHLkV1oBZ24nyL literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/img/material-design.svg b/src/i2p.chromium.base.profile/extensions/ublock.js/img/material-design.svg new file mode 100644 index 0000000..3b5e727 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/img/material-design.svg @@ -0,0 +1,16 @@ + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/img/photon.svg b/src/i2p.chromium.base.profile/extensions/ublock.js/img/photon.svg new file mode 100644 index 0000000..e6c42d5 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/img/photon.svg @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/img/ublock-defs.svg b/src/i2p.chromium.base.profile/extensions/ublock.js/img/ublock-defs.svg new file mode 100644 index 0000000..dbc59a3 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/img/ublock-defs.svg @@ -0,0 +1,27 @@ + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/img/ublock.svg b/src/i2p.chromium.base.profile/extensions/ublock.js/img/ublock.svg new file mode 100644 index 0000000..ea2ffd3 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/img/ublock.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/is-webrtc-supported.html b/src/i2p.chromium.base.profile/extensions/ublock.js/is-webrtc-supported.html new file mode 100644 index 0000000..d30b674 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/is-webrtc-supported.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/1p-filters.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/1p-filters.js new file mode 100644 index 0000000..e52beae --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/1p-filters.js @@ -0,0 +1,354 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +/* global CodeMirror, uDom, uBlockDashboard */ + +'use strict'; + +/******************************************************************************/ + +import './codemirror/ubo-static-filtering.js'; + +/******************************************************************************/ + +const cmEditor = new CodeMirror(document.getElementById('userFilters'), { + autoCloseBrackets: true, + autofocus: true, + extraKeys: { + 'Ctrl-Space': 'autocomplete', + 'Tab': 'toggleComment', + }, + foldGutter: true, + gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ], + lineNumbers: true, + lineWrapping: true, + matchBrackets: true, + maxScanLines: 1, + styleActiveLine: { + nonEmpty: true, + }, +}); + +uBlockDashboard.patchCodeMirrorEditor(cmEditor); + +let cachedUserFilters = ''; + +/******************************************************************************/ + +// Add auto-complete ability to the editor. + +{ + let hintUpdateToken = 0; + + const responseHandler = function(response) { + if ( response instanceof Object === false ) { return; } + if ( response.hintUpdateToken !== undefined ) { + const mode = cmEditor.getMode(); + if ( mode.setHints instanceof Function ) { + mode.setHints(response); + } + if ( hintUpdateToken === 0 ) { + mode.parser.expertMode = response.expertMode !== false; + } + hintUpdateToken = response.hintUpdateToken; + } + vAPI.setTimeout(getHints, 2503); + }; + + const getHints = function() { + vAPI.messaging.send('dashboard', { + what: 'getAutoCompleteDetails', + hintUpdateToken + }).then(responseHandler); + }; + + getHints(); +} + +/******************************************************************************/ + +const getEditorText = function() { + const text = cmEditor.getValue().replace(/\s+$/, ''); + return text === '' ? text : text + '\n'; +}; + +const setEditorText = function(text) { + cmEditor.setValue(text.replace(/\s+$/, '') + '\n\n'); +}; + +/******************************************************************************/ + +// This is to give a visual hint that the content of user blacklist has changed. + +const userFiltersChanged = function(changed) { + if ( typeof changed !== 'boolean' ) { + changed = self.hasUnsavedData(); + } + uDom.nodeFromId('userFiltersApply').disabled = !changed; + uDom.nodeFromId('userFiltersRevert').disabled = !changed; +}; + +/******************************************************************************/ + +// https://github.com/gorhill/uBlock/issues/3704 +// Merge changes to user filters occurring in the background with changes +// made in the editor. The code assumes that no deletion occurred in the +// background. + +const threeWayMerge = function(newContent) { + const prvContent = cachedUserFilters.trim().split(/\n/); + const differ = new self.diff_match_patch(); + const newChanges = differ.diff( + prvContent, + newContent.trim().split(/\n/) + ); + const usrChanges = differ.diff( + prvContent, + getEditorText().trim().split(/\n/) + ); + const out = []; + let i = 0, j = 0, k = 0; + while ( i < prvContent.length ) { + for ( ; j < newChanges.length; j++ ) { + const change = newChanges[j]; + if ( change[0] !== 1 ) { break; } + out.push(change[1]); + } + for ( ; k < usrChanges.length; k++ ) { + const change = usrChanges[k]; + if ( change[0] !== 1 ) { break; } + out.push(change[1]); + } + if ( k === usrChanges.length || usrChanges[k][0] !== -1 ) { + out.push(prvContent[i]); + } + i += 1; j += 1; k += 1; + } + for ( ; j < newChanges.length; j++ ) { + const change = newChanges[j]; + if ( change[0] !== 1 ) { continue; } + out.push(change[1]); + } + for ( ; k < usrChanges.length; k++ ) { + const change = usrChanges[k]; + if ( change[0] !== 1 ) { continue; } + out.push(change[1]); + } + return out.join('\n'); +}; + +/******************************************************************************/ + +const renderUserFilters = async function(merge = false) { + const details = await vAPI.messaging.send('dashboard', { + what: 'readUserFilters', + }); + if ( details instanceof Object === false || details.error ) { return; } + + const newContent = details.content.trim(); + + if ( merge && self.hasUnsavedData() ) { + setEditorText(threeWayMerge(newContent)); + userFiltersChanged(true); + } else { + setEditorText(newContent); + userFiltersChanged(false); + } + + cachedUserFilters = newContent; +}; + +/******************************************************************************/ + +const handleImportFilePicker = function() { + // https://github.com/chrisaljoudi/uBlock/issues/1004 + // Support extraction of filters from ABP backup file + const abpImporter = function(s) { + const reAbpSubscriptionExtractor = /\n\[Subscription\]\n+url=~[^\n]+([\x08-\x7E]*?)(?:\[Subscription\]|$)/ig; + const reAbpFilterExtractor = /\[Subscription filters\]([\x08-\x7E]*?)(?:\[Subscription\]|$)/i; + let matches = reAbpSubscriptionExtractor.exec(s); + // Not an ABP backup file + if ( matches === null ) { return s; } + const out = []; + do { + if ( matches.length === 2 ) { + let filterMatch = reAbpFilterExtractor.exec(matches[1].trim()); + if ( filterMatch !== null && filterMatch.length === 2 ) { + out.push(filterMatch[1].trim().replace(/\\\[/g, '[')); + } + } + matches = reAbpSubscriptionExtractor.exec(s); + } while ( matches !== null ); + return out.join('\n'); + }; + + const fileReaderOnLoadHandler = function() { + let content = abpImporter(this.result); + content = uBlockDashboard.mergeNewLines(getEditorText(), content); + cmEditor.operation(( ) => { + const cmPos = cmEditor.getCursor(); + setEditorText(content); + cmEditor.setCursor(cmPos); + cmEditor.focus(); + }); + }; + const file = this.files[0]; + if ( file === undefined || file.name === '' ) { return; } + if ( file.type.indexOf('text') !== 0 ) { return; } + const fr = new FileReader(); + fr.onload = fileReaderOnLoadHandler; + fr.readAsText(file); +}; + +/******************************************************************************/ + +const startImportFilePicker = function() { + const input = document.getElementById('importFilePicker'); + // Reset to empty string, this will ensure an change event is properly + // triggered if the user pick a file, even if it is the same as the last + // one picked. + input.value = ''; + input.click(); +}; + +/******************************************************************************/ + +const exportUserFiltersToFile = function() { + const val = getEditorText(); + if ( val === '' ) { return; } + const filename = vAPI.i18n('1pExportFilename') + .replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString()) + .replace(/ +/g, '_'); + vAPI.download({ + 'url': 'data:text/plain;charset=utf-8,' + encodeURIComponent(val + '\n'), + 'filename': filename + }); +}; + +/******************************************************************************/ + +const applyChanges = async function() { + const details = await vAPI.messaging.send('dashboard', { + what: 'writeUserFilters', + content: getEditorText(), + }); + if ( details instanceof Object === false || details.error ) { return; } + + cachedUserFilters = details.content.trim(); + userFiltersChanged(false); + vAPI.messaging.send('dashboard', { + what: 'reloadAllFilters', + }); +}; + +const revertChanges = function() { + setEditorText(cachedUserFilters); +}; + +/******************************************************************************/ + +const getCloudData = function() { + return getEditorText(); +}; + +const setCloudData = function(data, append) { + if ( typeof data !== 'string' ) { return; } + if ( append ) { + data = uBlockDashboard.mergeNewLines(getEditorText(), data); + } + cmEditor.setValue(data); +}; + +self.cloud.onPush = getCloudData; +self.cloud.onPull = setCloudData; + +/******************************************************************************/ + +self.hasUnsavedData = function() { + return getEditorText().trim() !== cachedUserFilters; +}; + +/******************************************************************************/ + +// Handle user interaction +uDom('#importUserFiltersFromFile').on('click', startImportFilePicker); +uDom('#importFilePicker').on('change', handleImportFilePicker); +uDom('#exportUserFiltersToFile').on('click', exportUserFiltersToFile); +uDom('#userFiltersApply').on('click', ( ) => { applyChanges(); }); +uDom('#userFiltersRevert').on('click', revertChanges); + +(async ( ) => { + await renderUserFilters(); + + cmEditor.clearHistory(); + + // https://github.com/gorhill/uBlock/issues/3706 + // Save/restore cursor position + { + const line = + await vAPI.localStorage.getItemAsync('myFiltersCursorPosition'); + if ( typeof line === 'number' ) { + cmEditor.setCursor(line, 0); + } + } + + // https://github.com/gorhill/uBlock/issues/3706 + // Save/restore cursor position + { + let curline = 0; + let timer; + cmEditor.on('cursorActivity', ( ) => { + if ( timer !== undefined ) { return; } + if ( cmEditor.getCursor().line === curline ) { return; } + timer = vAPI.setTimeout(( ) => { + timer = undefined; + curline = cmEditor.getCursor().line; + vAPI.localStorage.setItem('myFiltersCursorPosition', curline); + }, 701); + }); + } + + // https://github.com/gorhill/uBlock/issues/3704 + // Merge changes to user filters occurring in the background + vAPI.broadcastListener.add(msg => { + switch ( msg.what ) { + case 'userFiltersUpdated': { + cmEditor.startOperation(); + const scroll = cmEditor.getScrollInfo(); + const selections = cmEditor.listSelections(); + renderUserFilters(true).then(( ) => { + cmEditor.clearHistory(); + cmEditor.setSelection(selections[0].anchor, selections[0].head); + cmEditor.scrollTo(scroll.left, scroll.top); + cmEditor.endOperation(); + }); + break; + } + default: + break; + } + }); +})(); + +cmEditor.on('changes', userFiltersChanged); +CodeMirror.commands.save = applyChanges; + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/3p-filters.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/3p-filters.js new file mode 100644 index 0000000..924159c --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/3p-filters.js @@ -0,0 +1,717 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +/* global uDom */ + +'use strict'; + +/******************************************************************************/ + +{ +// >>>>> start of local scope + +/******************************************************************************/ + +const lastUpdateTemplateString = vAPI.i18n('3pLastUpdate'); +const obsoleteTemplateString = vAPI.i18n('3pExternalListObsolete'); +const reValidExternalList = /^[a-z-]+:\/\/(?:\S+\/\S*|\/\S+)/m; + +let listDetails = {}; +let filteringSettingsHash = ''; +let hideUnusedSet = new Set([ '*' ]); + +/******************************************************************************/ + +const messaging = vAPI.messaging; + +vAPI.broadcastListener.add(msg => { + switch ( msg.what ) { + case 'assetUpdated': + updateAssetStatus(msg); + break; + case 'assetsUpdated': + document.body.classList.remove('updating'); + renderWidgets(); + break; + case 'staticFilteringDataChanged': + renderFilterLists(); + break; + default: + break; + } +}); + +/******************************************************************************/ + +const renderNumber = function(value) { + return value.toLocaleString(); +}; + +/******************************************************************************/ + +const renderFilterLists = function(soft) { + const listGroupTemplate = uDom('#templates .groupEntry'); + const listEntryTemplate = uDom('#templates .listEntry'); + const listStatsTemplate = vAPI.i18n('3pListsOfBlockedHostsPerListStats'); + const renderElapsedTimeToString = vAPI.i18n.renderElapsedTimeToString; + const groupNames = new Map([ [ 'user', '' ] ]); + + // Assemble a pretty list name if possible + const listNameFromListKey = function(listKey) { + const list = listDetails.current[listKey] || listDetails.available[listKey]; + const listTitle = list ? list.title : ''; + if ( listTitle === '' ) { return listKey; } + return listTitle; + }; + + const liFromListEntry = function(listKey, li, hideUnused) { + const entry = listDetails.available[listKey]; + if ( !li ) { + li = listEntryTemplate.clone().nodeAt(0); + } + const on = entry.off !== true; + li.classList.toggle('checked', on); + let elem; + if ( li.getAttribute('data-listkey') !== listKey ) { + li.setAttribute('data-listkey', listKey); + elem = li.querySelector('input[type="checkbox"]'); + elem.checked = on; + elem = li.querySelector('.listname'); + elem.textContent = listNameFromListKey(listKey); + elem = li.querySelector('a.content'); + elem.setAttribute('href', 'asset-viewer.html?url=' + encodeURIComponent(listKey)); + elem.setAttribute('type', 'text/html'); + li.classList.remove('toRemove'); + if ( entry.supportName ) { + li.classList.add('support'); + elem = li.querySelector('a.support'); + elem.setAttribute('href', entry.supportURL); + elem.setAttribute('title', entry.supportName); + } else { + li.classList.remove('support'); + } + if ( entry.external ) { + li.classList.add('external'); + } else { + li.classList.remove('external'); + } + if ( entry.instructionURL ) { + li.classList.add('mustread'); + elem = li.querySelector('a.mustread'); + elem.setAttribute('href', entry.instructionURL); + } else { + li.classList.remove('mustread'); + } + li.classList.toggle('isDefault', entry.isDefault === true); + li.classList.toggle('unused', hideUnused && !on); + } + // https://github.com/gorhill/uBlock/issues/1429 + if ( !soft ) { + li.querySelector('input[type="checkbox"]').checked = on; + } + elem = li.querySelector('span.counts'); + let text = ''; + if ( !isNaN(+entry.entryUsedCount) && !isNaN(+entry.entryCount) ) { + text = listStatsTemplate + .replace('{{used}}', renderNumber(on ? entry.entryUsedCount : 0)) + .replace('{{total}}', renderNumber(entry.entryCount)); + } + elem.textContent = text; + // https://github.com/chrisaljoudi/uBlock/issues/104 + const asset = listDetails.cache[listKey] || {}; + const remoteURL = asset.remoteURL; + li.classList.toggle( + 'unsecure', + typeof remoteURL === 'string' && remoteURL.lastIndexOf('http:', 0) === 0 + ); + li.classList.toggle('failed', asset.error !== undefined); + li.classList.toggle('obsolete', asset.obsolete === true); + const lastUpdateString = lastUpdateTemplateString.replace( + '{{ago}}', + renderElapsedTimeToString(asset.writeTime || 0) + ); + if ( asset.obsolete === true ) { + let title = obsoleteTemplateString; + if ( asset.cached && asset.writeTime !== 0 ) { + title += '\n' + lastUpdateString; + } + li.querySelector('.status.obsolete').setAttribute('title', title); + } + if ( asset.cached === true ) { + li.classList.add('cached'); + li.querySelector('.status.cache').setAttribute( + 'title', + lastUpdateString + ); + } else { + li.classList.remove('cached'); + } + li.classList.remove('discard'); + return li; + }; + + const listEntryCountFromGroup = function(listKeys) { + if ( Array.isArray(listKeys) === false ) { return ''; } + let count = 0, + total = 0; + for ( const listKey of listKeys ) { + if ( listDetails.available[listKey].off !== true ) { + count += 1; + } + total += 1; + } + return total !== 0 ? + `(${count.toLocaleString()}/${total.toLocaleString()})` : + ''; + }; + + const liFromListGroup = function(groupKey, listKeys) { + let liGroup = document.querySelector(`#lists > .groupEntry[data-groupkey="${groupKey}"]`); + if ( liGroup === null ) { + liGroup = listGroupTemplate.clone().nodeAt(0); + let groupName = groupNames.get(groupKey); + if ( groupName === undefined ) { + groupName = vAPI.i18n('3pGroup' + groupKey.charAt(0).toUpperCase() + groupKey.slice(1)); + groupNames.set(groupKey, groupName); + } + if ( groupName !== '' ) { + liGroup.querySelector('.geName').textContent = groupName; + } + } + if ( liGroup.querySelector('.geName:empty') === null ) { + liGroup.querySelector('.geCount').textContent = listEntryCountFromGroup(listKeys); + } + let hideUnused = mustHideUnusedLists(groupKey); + liGroup.classList.toggle('hideUnused', hideUnused); + let ulGroup = liGroup.querySelector('.listEntries'); + if ( !listKeys ) { return liGroup; } + listKeys.sort(function(a, b) { + return (listDetails.available[a].title || '').localeCompare(listDetails.available[b].title || ''); + }); + for ( let i = 0; i < listKeys.length; i++ ) { + let liEntry = liFromListEntry( + listKeys[i], + ulGroup.children[i], + hideUnused + ); + if ( liEntry.parentElement === null ) { + ulGroup.appendChild(liEntry); + } + } + return liGroup; + }; + + const groupsFromLists = function(lists) { + let groups = new Map(); + let listKeys = Object.keys(lists); + for ( let listKey of listKeys ) { + let list = lists[listKey]; + let groupKey = list.group || 'nogroup'; + if ( groupKey === 'social' ) { + groupKey = 'annoyances'; + } + let memberKeys = groups.get(groupKey); + if ( memberKeys === undefined ) { + groups.set(groupKey, (memberKeys = [])); + } + memberKeys.push(listKey); + } + return groups; + }; + + const onListsReceived = function(details) { + // Before all, set context vars + listDetails = details; + + // "My filters" will now sit in its own group. The following code + // ensures smooth transition. + listDetails.available['user-filters'].group = 'user'; + + // Incremental rendering: this will allow us to easily discard unused + // DOM list entries. + uDom('#lists .listEntries .listEntry[data-listkey]').addClass('discard'); + + // Remove import widget while we recreate list of lists. + const importWidget = uDom('.listEntry.toImport').detach(); + + // Visually split the filter lists in purpose-based groups + const ulLists = document.querySelector('#lists'); + const groups = groupsFromLists(details.available); + const groupKeys = [ + 'user', + 'default', + 'ads', + 'privacy', + 'malware', + 'annoyances', + 'multipurpose', + 'regions', + 'custom' + ]; + document.body.classList.toggle('hideUnused', mustHideUnusedLists('*')); + for ( let i = 0; i < groupKeys.length; i++ ) { + let groupKey = groupKeys[i]; + let liGroup = liFromListGroup(groupKey, groups.get(groupKey)); + liGroup.setAttribute('data-groupkey', groupKey); + if ( liGroup.parentElement === null ) { + ulLists.appendChild(liGroup); + } + groups.delete(groupKey); + } + // For all groups not covered above (if any left) + for ( const groupKey of Object.keys(groups) ) { + ulLists.appendChild(liFromListGroup(groupKey, groupKey)); + } + + uDom('#lists .listEntries .listEntry.discard').remove(); + + // Re-insert import widget. + uDom('[data-groupkey="custom"] .listEntries').append(importWidget); + + uDom.nodeFromId('autoUpdate').checked = + listDetails.autoUpdate === true; + uDom.nodeFromId('listsOfBlockedHostsPrompt').textContent = + vAPI.i18n('3pListsOfBlockedHostsPrompt') + .replace( + '{{netFilterCount}}', + renderNumber(details.netFilterCount) + ) + .replace( + '{{cosmeticFilterCount}}', + renderNumber(details.cosmeticFilterCount) + ); + uDom.nodeFromId('parseCosmeticFilters').checked = + listDetails.parseCosmeticFilters === true; + uDom.nodeFromId('ignoreGenericCosmeticFilters').checked = + listDetails.ignoreGenericCosmeticFilters === true; + uDom.nodeFromId('suspendUntilListsAreLoaded').checked = + listDetails.suspendUntilListsAreLoaded === true; + + // Compute a hash of the settings so that we can keep track of changes + // affecting the loading of filter lists. + if ( !soft ) { + filteringSettingsHash = hashFromCurrentFromSettings(); + } + + // https://github.com/gorhill/uBlock/issues/2394 + document.body.classList.toggle('updating', listDetails.isUpdating); + + renderWidgets(); + }; + + messaging.send('dashboard', { + what: 'getLists', + }).then(details => { + onListsReceived(details); + }); +}; + +/******************************************************************************/ + +const renderWidgets = function() { + let cl = uDom.nodeFromId('buttonApply').classList; + cl.toggle( + 'disabled', + filteringSettingsHash === hashFromCurrentFromSettings() + ); + const updating = document.body.classList.contains('updating'); + cl = uDom.nodeFromId('buttonUpdate').classList; + cl.toggle('active', updating); + cl.toggle( + 'disabled', + updating === false && + document.querySelector('#lists .listEntry.obsolete:not(.toRemove) input[type="checkbox"]:checked') === null + ); + cl = uDom.nodeFromId('buttonPurgeAll').classList; + cl.toggle( + 'disabled', + updating || document.querySelector('#lists .listEntry.cached:not(.obsolete)') === null + ); +}; + +/******************************************************************************/ + +const updateAssetStatus = function(details) { + const li = document.querySelector( + '#lists .listEntry[data-listkey="' + details.key + '"]' + ); + if ( li === null ) { return; } + li.classList.toggle('failed', !!details.failed); + li.classList.toggle('obsolete', !details.cached); + li.classList.toggle('cached', !!details.cached); + if ( details.cached ) { + li.querySelector('.status.cache').setAttribute( + 'title', + lastUpdateTemplateString.replace( + '{{ago}}', + vAPI.i18n.renderElapsedTimeToString(Date.now()) + ) + ); + } + renderWidgets(); +}; + +/******************************************************************************* + + Compute a hash from all the settings affecting how filter lists are loaded + in memory. + +**/ + +const hashFromCurrentFromSettings = function() { + const hash = [ + uDom.nodeFromId('parseCosmeticFilters').checked, + uDom.nodeFromId('ignoreGenericCosmeticFilters').checked + ]; + const listHash = []; + const listEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)'); + for ( const liEntry of listEntries ) { + if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) { + listHash.push(liEntry.getAttribute('data-listkey')); + } + } + hash.push( + listHash.sort().join(), + uDom.nodeFromId('importLists').checked && + reValidExternalList.test(uDom.nodeFromId('externalLists').value), + document.querySelector('#lists .listEntry.toRemove') !== null + ); + return hash.join(); +}; + +/******************************************************************************/ + +const onListsetChanged = function(ev) { + const input = ev.target; + const li = input.closest('.listEntry'); + li.classList.toggle('checked', input.checked); + onFilteringSettingsChanged(); +}; + +/******************************************************************************/ + +const onFilteringSettingsChanged = function() { + renderWidgets(); +}; + +/******************************************************************************/ + +const onRemoveExternalList = function(ev) { + const liEntry = ev.target.closest('[data-listkey]'); + if ( liEntry === null ) { return; } + liEntry.classList.toggle('toRemove'); + renderWidgets(); +}; + +/******************************************************************************/ + +const onPurgeClicked = function(ev) { + const liEntry = ev.target.closest('[data-listkey]'); + const listKey = liEntry.getAttribute('data-listkey') || ''; + if ( listKey === '' ) { return; } + + messaging.send('dashboard', { + what: 'purgeCache', + assetKey: listKey, + }); + + // If the cached version is purged, the installed version must be assumed + // to be obsolete. + // https://github.com/gorhill/uBlock/issues/1733 + // An external filter list must not be marked as obsolete, they will + // always be fetched anyways if there is no cached copy. + liEntry.classList.add('obsolete'); + liEntry.classList.remove('cached'); + + if ( liEntry.querySelector('input[type="checkbox"]').checked ) { + renderWidgets(); + } +}; + +/******************************************************************************/ + +const selectFilterLists = async function() { + // Cosmetic filtering switch + messaging.send('dashboard', { + what: 'userSettings', + name: 'parseAllABPHideFilters', + value: uDom.nodeFromId('parseCosmeticFilters').checked, + }); + messaging.send('dashboard', { + what: 'userSettings', + name: 'ignoreGenericCosmeticFilters', + value: uDom.nodeFromId('ignoreGenericCosmeticFilters').checked, + }); + + // Filter lists to select + const toSelect = []; + for ( + const liEntry of + document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)') + ) { + if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) { + toSelect.push(liEntry.getAttribute('data-listkey')); + } + } + + // External filter lists to remove + const toRemove = []; + for ( + const liEntry of + document.querySelectorAll('#lists .listEntry.toRemove[data-listkey]') + ) { + toRemove.push(liEntry.getAttribute('data-listkey')); + } + + // External filter lists to import + const externalListsElem = document.getElementById('externalLists'); + const toImport = externalListsElem.value.trim(); + { + const liEntry = externalListsElem.closest('.listEntry'); + liEntry.classList.remove('checked'); + liEntry.querySelector('input[type="checkbox"]').checked = false; + externalListsElem.value = ''; + } + + await messaging.send('dashboard', { + what: 'applyFilterListSelection', + toSelect: toSelect, + toImport: toImport, + toRemove: toRemove, + }); + + filteringSettingsHash = hashFromCurrentFromSettings(); +}; + +/******************************************************************************/ + +const buttonApplyHandler = async function() { + uDom('#buttonApply').removeClass('enabled'); + await selectFilterLists(); + renderWidgets(); + messaging.send('dashboard', { what: 'reloadAllFilters' }); +}; + +/******************************************************************************/ + +const buttonUpdateHandler = async function() { + await selectFilterLists(); + document.body.classList.add('updating'); + renderWidgets(); + messaging.send('dashboard', { what: 'forceUpdateAssets' }); +}; + +/******************************************************************************/ + +const buttonPurgeAllHandler = async function(hard) { + uDom('#buttonPurgeAll').removeClass('enabled'); + await messaging.send('dashboard', { + what: 'purgeAllCaches', + hard, + }); + renderFilterLists(true); +}; + +/******************************************************************************/ + +const userSettingCheckboxChanged = function() { + const target = event.target; + messaging.send('dashboard', { + what: 'userSettings', + name: target.id, + value: target.checked, + }); +}; + +/******************************************************************************/ + +// Collapsing of unused lists. + +const mustHideUnusedLists = function(which) { + const hideAll = hideUnusedSet.has('*'); + if ( which === '*' ) { return hideAll; } + return hideUnusedSet.has(which) !== hideAll; +}; + +const toggleHideUnusedLists = function(which) { + const doesHideAll = hideUnusedSet.has('*'); + let groupSelector; + let mustHide; + if ( which === '*' ) { + mustHide = doesHideAll === false; + groupSelector = ''; + hideUnusedSet.clear(); + if ( mustHide ) { + hideUnusedSet.add(which); + } + document.body.classList.toggle('hideUnused', mustHide); + uDom('.groupEntry[data-groupkey]').toggleClass('hideUnused', mustHide); + } else { + const doesHide = hideUnusedSet.has(which); + if ( doesHide ) { + hideUnusedSet.delete(which); + } else { + hideUnusedSet.add(which); + } + mustHide = doesHide === doesHideAll; + groupSelector = '.groupEntry[data-groupkey="' + which + '"] '; + uDom(groupSelector).toggleClass('hideUnused', mustHide); + } + uDom(groupSelector + '.listEntry input[type="checkbox"]:not(:checked)') + .ancestors('.listEntry[data-listkey]') + .toggleClass('unused', mustHide); + vAPI.localStorage.setItem( + 'hideUnusedFilterLists', + Array.from(hideUnusedSet) + ); +}; + +const revealHiddenUsedLists = function() { + uDom('#lists .listEntry.unused input[type="checkbox"]:checked') + .ancestors('.listEntry[data-listkey]') + .removeClass('unused'); +}; + +uDom('#listsOfBlockedHostsPrompt').on('click', function() { + toggleHideUnusedLists('*'); +}); + +uDom('#lists').on('click', '.groupEntry[data-groupkey] > .geDetails', function(ev) { + toggleHideUnusedLists( + uDom(ev.target) + .ancestors('.groupEntry[data-groupkey]') + .attr('data-groupkey') + ); +}); + +// Initialize from saved state. +vAPI.localStorage.getItemAsync('hideUnusedFilterLists').then(value => { + if ( Array.isArray(value) ) { + hideUnusedSet = new Set(value); + } +}); + +/******************************************************************************/ + +// Cloud-related. + +const toCloudData = function() { + const bin = { + parseCosmeticFilters: uDom.nodeFromId('parseCosmeticFilters').checked, + ignoreGenericCosmeticFilters: uDom.nodeFromId('ignoreGenericCosmeticFilters').checked, + selectedLists: [] + }; + + const liEntries = document.querySelectorAll('#lists .listEntry'); + for ( const liEntry of liEntries ) { + if ( liEntry.querySelector('input').checked ) { + bin.selectedLists.push(liEntry.getAttribute('data-listkey')); + } + } + + return bin; +}; + +const fromCloudData = function(data, append) { + if ( typeof data !== 'object' || data === null ) { return; } + + let elem, checked; + + elem = uDom.nodeFromId('parseCosmeticFilters'); + checked = data.parseCosmeticFilters === true || append && elem.checked; + elem.checked = listDetails.parseCosmeticFilters = checked; + + elem = uDom.nodeFromId('ignoreGenericCosmeticFilters'); + checked = data.ignoreGenericCosmeticFilters === true || append && elem.checked; + elem.checked = listDetails.ignoreGenericCosmeticFilters = checked; + + const selectedSet = new Set(data.selectedLists); + const listEntries = uDom('#lists .listEntry'); + for ( let i = 0, n = listEntries.length; i < n; i++ ) { + const listEntry = listEntries.at(i); + const listKey = listEntry.attr('data-listkey'); + const hasListKey = selectedSet.has(listKey); + selectedSet.delete(listKey); + const input = listEntry.descendants('input').first(); + if ( append && input.prop('checked') ) { continue; } + input.prop('checked', hasListKey); + } + + // If there are URL-like list keys left in the selected set, import them. + for ( const listKey of selectedSet ) { + if ( reValidExternalList.test(listKey) === false ) { + selectedSet.delete(listKey); + } + } + if ( selectedSet.size !== 0 ) { + elem = uDom.nodeFromId('externalLists'); + if ( append ) { + if ( elem.value.trim() !== '' ) { elem.value += '\n'; } + } else { + elem.value = ''; + } + elem.value += Array.from(selectedSet).join('\n'); + uDom.nodeFromId('importLists').checked = true; + } + + revealHiddenUsedLists(); + renderWidgets(); +}; + +self.cloud.onPush = toCloudData; +self.cloud.onPull = fromCloudData; + +/******************************************************************************/ + +self.hasUnsavedData = function() { + return hashFromCurrentFromSettings() !== filteringSettingsHash; +}; + +/******************************************************************************/ + +uDom('#autoUpdate').on('change', userSettingCheckboxChanged); +uDom('#parseCosmeticFilters').on('change', onFilteringSettingsChanged); +uDom('#ignoreGenericCosmeticFilters').on('change', onFilteringSettingsChanged); +uDom('#suspendUntilListsAreLoaded').on('change', userSettingCheckboxChanged); +uDom('#buttonApply').on('click', ( ) => { buttonApplyHandler(); }); +uDom('#buttonUpdate').on('click', ( ) => { buttonUpdateHandler(); }); +uDom('#buttonPurgeAll').on('click', ev => { + buttonPurgeAllHandler(ev.shiftKey); +}); +uDom('#lists').on('change', '.listEntry input', onListsetChanged); +uDom('#lists').on('click', '.listEntry .remove', onRemoveExternalList); +uDom('#lists').on('click', 'span.cache', onPurgeClicked); +uDom('#externalLists').on('input', onFilteringSettingsChanged); + +uDom('#lists').on('click', '.listEntry label *', ev => { + if ( ev.target.matches('a,input,.forinput') ) { return; } + ev.preventDefault(); +}); + +/******************************************************************************/ + +renderFilterLists(); + +/******************************************************************************/ + +// <<<<< end of local scope +} + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/about.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/about.js new file mode 100644 index 0000000..1537945 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/about.js @@ -0,0 +1,34 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +/* global uDom */ + +'use strict'; + +/******************************************************************************/ + +(async ( ) => { + const appData = await vAPI.messaging.send('dashboard', { + what: 'getAppData', + }); + + uDom('#aboutNameVer').text(appData.name + ' ' + appData.version); +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/advanced-settings.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/advanced-settings.js new file mode 100644 index 0000000..a7cd8b9 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/advanced-settings.js @@ -0,0 +1,211 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2016-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 +*/ + +/* global CodeMirror, uDom, uBlockDashboard */ + +'use strict'; + +/******************************************************************************/ + +{ +// >>>> Start of private namespace + +/******************************************************************************/ + +let defaultSettings = new Map(); +let adminSettings = new Map(); +let beforeHash = ''; + +/******************************************************************************/ + +CodeMirror.defineMode('raw-settings', function() { + let lastSetting = ''; + + return { + token: function(stream) { + if ( stream.sol() ) { + stream.eatSpace(); + const match = stream.match(/\S+/); + if ( match !== null && defaultSettings.has(match[0]) ) { + lastSetting = match[0]; + return adminSettings.has(match[0]) + ? 'readonly keyword' + : 'keyword'; + } + stream.skipToEnd(); + return 'line-cm-error'; + } + stream.eatSpace(); + const match = stream.match(/.*$/); + if ( match !== null ) { + if ( match[0].trim() !== defaultSettings.get(lastSetting) ) { + return 'line-cm-strong'; + } + if ( adminSettings.has(lastSetting) ) { + return 'readonly'; + } + } + stream.skipToEnd(); + return null; + } + }; +}); + +const cmEditor = new CodeMirror( + document.getElementById('advancedSettings'), + { + autofocus: true, + lineNumbers: true, + lineWrapping: false, + styleActiveLine: true + } +); + +uBlockDashboard.patchCodeMirrorEditor(cmEditor); + +/******************************************************************************/ + +const hashFromAdvancedSettings = function(raw) { + const aa = typeof raw === 'string' + ? arrayFromString(raw) + : arrayFromObject(raw); + aa.sort((a, b) => a[0].localeCompare(b[0])); + return JSON.stringify(aa); +}; + +/******************************************************************************/ + +const arrayFromObject = function(o) { + const out = []; + for ( const k in o ) { + if ( o.hasOwnProperty(k) === false ) { continue; } + out.push([ k, `${o[k]}` ]); + } + return out; +}; + +const arrayFromString = function(s) { + const out = []; + for ( let line of s.split(/[\n\r]+/) ) { + line = line.trim(); + if ( line === '' ) { continue; } + const pos = line.indexOf(' '); + let k, v; + if ( pos !== -1 ) { + k = line.slice(0, pos); + v = line.slice(pos + 1); + } else { + k = line; + v = ''; + } + out.push([ k.trim(), v.trim() ]); + } + return out; +}; + +/******************************************************************************/ + +// This is to give a visual hint that the content of user blacklist has changed. + +const advancedSettingsChanged = (( ) => { + let timer; + + const handler = ( ) => { + timer = undefined; + const changed = + hashFromAdvancedSettings(cmEditor.getValue()) !== beforeHash; + uDom.nodeFromId('advancedSettingsApply').disabled = !changed; + CodeMirror.commands.save = changed ? applyChanges : function(){}; + }; + + return function() { + if ( timer !== undefined ) { clearTimeout(timer); } + timer = vAPI.setTimeout(handler, 100); + }; +})(); + +cmEditor.on('changes', advancedSettingsChanged); + +/******************************************************************************/ + +const renderAdvancedSettings = async function(first) { + const details = await vAPI.messaging.send('dashboard', { + what: 'readHiddenSettings', + }); + defaultSettings = new Map(arrayFromObject(details.default)); + adminSettings = new Map(arrayFromObject(details.admin)); + beforeHash = hashFromAdvancedSettings(details.current); + const pretty = []; + const roLines = []; + const entries = arrayFromObject(details.current); + let max = 0; + for ( const [ k ] of entries ) { + if ( k.length > max ) { max = k.length; } + } + for ( let i = 0; i < entries.length; i++ ) { + const [ k, v ] = entries[i]; + pretty.push(' '.repeat(max - k.length) + `${k} ${v}`); + if ( adminSettings.has(k) ) { + roLines.push(i); + } + } + pretty.push(''); + cmEditor.setValue(pretty.join('\n')); + if ( first ) { + cmEditor.clearHistory(); + } + for ( const line of roLines ) { + cmEditor.markText( + { line, ch: 0 }, + { line: line + 1, ch: 0 }, + { readOnly: true } + ); + } + advancedSettingsChanged(); + cmEditor.focus(); +}; + +/******************************************************************************/ + +const applyChanges = async function() { + await vAPI.messaging.send('dashboard', { + what: 'writeHiddenSettings', + content: cmEditor.getValue(), + }); + renderAdvancedSettings(); +}; + +/******************************************************************************/ + +uDom.nodeFromId('advancedSettings').addEventListener( + 'input', + advancedSettingsChanged +); +uDom.nodeFromId('advancedSettingsApply').addEventListener('click', ( ) => { + applyChanges(); +}); + +renderAdvancedSettings(true); + +/******************************************************************************/ + +// <<<< End of private namespace +} diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/asset-viewer.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/asset-viewer.js new file mode 100644 index 0000000..ef0824d --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/asset-viewer.js @@ -0,0 +1,108 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +/* global CodeMirror, uBlockDashboard */ + +'use strict'; + +/******************************************************************************/ + +import './codemirror/ubo-static-filtering.js'; + +/******************************************************************************/ + +(async ( ) => { + const subscribeURL = new URL(document.location); + const subscribeParams = subscribeURL.searchParams; + const assetKey = subscribeParams.get('url'); + if ( assetKey === null ) { return; } + + const subscribeElem = subscribeParams.get('subscribe') !== null + ? document.getElementById('subscribe') + : null; + if ( subscribeElem !== null && subscribeURL.hash !== '#subscribed' ) { + const title = subscribeParams.get('title'); + const promptElem = document.getElementById('subscribePrompt'); + promptElem.children[0].textContent = title; + const a = promptElem.children[1]; + a.textContent = assetKey; + a.setAttribute('href', assetKey); + subscribeElem.classList.remove('hide'); + } + + const cmEditor = new CodeMirror(document.getElementById('content'), { + autofocus: true, + foldGutter: true, + gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ], + lineNumbers: true, + lineWrapping: true, + matchBrackets: true, + maxScanLines: 1, + readOnly: true, + styleActiveLine: { + nonEmpty: true, + }, + }); + + uBlockDashboard.patchCodeMirrorEditor(cmEditor); + + const hints = await vAPI.messaging.send('dashboard', { + what: 'getAutoCompleteDetails' + }); + if ( hints instanceof Object ) { + const mode = cmEditor.getMode(); + if ( mode.setHints instanceof Function ) { + mode.setHints(hints); + } + } + + const details = await vAPI.messaging.send('default', { + what : 'getAssetContent', + url: assetKey, + }); + cmEditor.setValue(details && details.content || ''); + + if ( subscribeElem !== null ) { + document.getElementById('subscribeButton').addEventListener( + 'click', + ( ) => { + subscribeElem.classList.add('hide'); + vAPI.messaging.send('scriptlets', { + what: 'applyFilterListSelection', + toImport: assetKey, + }).then(( ) => { + vAPI.messaging.send('scriptlets', { + what: 'reloadAllFilters' + }); + }); + }, + { once: true } + ); + } + + if ( details.sourceURL ) { + const a = document.querySelector('.cm-search-widget .sourceURL'); + a.setAttribute('href', details.sourceURL); + a.setAttribute('title', details.sourceURL); + } + + document.body.classList.remove('loading'); +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/assets.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/assets.js new file mode 100644 index 0000000..576dc1d --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/assets.js @@ -0,0 +1,1062 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +'use strict'; + +/******************************************************************************/ + +import cacheStorage from './cachestorage.js'; +import logger from './logger.js'; +import µb from './background.js'; + +/******************************************************************************/ + +const reIsExternalPath = /^(?:[a-z-]+):\/\//; +const reIsUserAsset = /^user-/; +const errorCantConnectTo = vAPI.i18n('errorCantConnectTo'); + +const assets = {}; + +// A hint for various pieces of code to take measures if possible to save +// bandwidth of remote servers. +let remoteServerFriendly = false; + +/******************************************************************************/ + +const observers = []; + +assets.addObserver = function(observer) { + if ( observers.indexOf(observer) === -1 ) { + observers.push(observer); + } +}; + +assets.removeObserver = function(observer) { + let pos; + while ( (pos = observers.indexOf(observer)) !== -1 ) { + observers.splice(pos, 1); + } +}; + +const fireNotification = function(topic, details) { + let result; + for ( const observer of observers ) { + const r = observer(topic, details); + if ( r !== undefined ) { result = r; } + } + return result; +}; + +/******************************************************************************/ + +assets.fetch = function(url, options = {}) { + return new Promise((resolve, reject) => { + // Start of executor + + const timeoutAfter = µb.hiddenSettings.assetFetchTimeout * 1000 || 30000; + const xhr = new XMLHttpRequest(); + let contentLoaded = 0; + let timeoutTimer; + + const cleanup = function() { + xhr.removeEventListener('load', onLoadEvent); + xhr.removeEventListener('error', onErrorEvent); + xhr.removeEventListener('abort', onErrorEvent); + xhr.removeEventListener('progress', onProgressEvent); + if ( timeoutTimer !== undefined ) { + clearTimeout(timeoutTimer); + timeoutTimer = undefined; + } + }; + + const fail = function(details, msg) { + logger.writeOne({ + realm: 'message', + type: 'error', + text: msg, + }); + details.content = ''; + details.error = msg; + reject(details); + }; + + // https://github.com/gorhill/uMatrix/issues/15 + const onLoadEvent = function() { + cleanup(); + // xhr for local files gives status 0, but actually succeeds + const details = { + url, + statusCode: this.status || 200, + statusText: this.statusText || '' + }; + if ( details.statusCode < 200 || details.statusCode >= 300 ) { + return fail(details, `${url}: ${details.statusCode} ${details.statusText}`); + } + details.content = this.response; + resolve(details); + }; + + const onErrorEvent = function() { + cleanup(); + fail({ url }, errorCantConnectTo.replace('{{msg}}', url)); + }; + + const onTimeout = function() { + xhr.abort(); + }; + + // https://github.com/gorhill/uBlock/issues/2526 + // - Timeout only when there is no progress. + const onProgressEvent = function(ev) { + if ( ev.loaded === contentLoaded ) { return; } + contentLoaded = ev.loaded; + if ( timeoutTimer !== undefined ) { + clearTimeout(timeoutTimer); + } + timeoutTimer = vAPI.setTimeout(onTimeout, timeoutAfter); + }; + + // Be ready for thrown exceptions: + // I am pretty sure it used to work, but now using a URL such as + // `file:///` on Chromium 40 results in an exception being thrown. + try { + xhr.open('get', url, true); + xhr.addEventListener('load', onLoadEvent); + xhr.addEventListener('error', onErrorEvent); + xhr.addEventListener('abort', onErrorEvent); + xhr.addEventListener('progress', onProgressEvent); + xhr.responseType = options.responseType || 'text'; + xhr.send(); + timeoutTimer = vAPI.setTimeout(onTimeout, timeoutAfter); + } catch (e) { + onErrorEvent.call(xhr); + } + + // End of executor + }); +}; + +/******************************************************************************/ + +assets.fetchText = async function(url) { + const isExternal = reIsExternalPath.test(url); + let actualUrl = isExternal ? url : vAPI.getURL(url); + + // https://github.com/gorhill/uBlock/issues/2592 + // Force browser cache to be bypassed, but only for resources which have + // been fetched more than one hour ago. + // https://github.com/uBlockOrigin/uBlock-issues/issues/682#issuecomment-515197130 + // Provide filter list authors a way to completely bypass + // the browser cache. + // https://github.com/gorhill/uBlock/commit/048bfd251c9b#r37972005 + // Use modulo prime numbers to avoid generating the same token at the + // same time across different days. + // Do not bypass browser cache if we are asked to be gentle on remote + // servers. + if ( isExternal && remoteServerFriendly !== true ) { + const cacheBypassToken = + µb.hiddenSettings.updateAssetBypassBrowserCache + ? Math.floor(Date.now() / 1000) % 86413 + : Math.floor(Date.now() / 3600000) % 13; + const queryValue = `_=${cacheBypassToken}`; + if ( actualUrl.indexOf('?') === -1 ) { + actualUrl += '?'; + } else { + actualUrl += '&'; + } + actualUrl += queryValue; + } + + let details = { content: '' }; + try { + details = await assets.fetch(actualUrl); + + // Consider an empty result to be an error + if ( stringIsNotEmpty(details.content) === false ) { + details.content = ''; + } + + // We never download anything else than plain text: discard if + // response appears to be a HTML document: could happen when server + // serves some kind of error page for example. + const text = details.content.trim(); + if ( text.startsWith('<') && text.endsWith('>') ) { + details.content = ''; + } + + // Important: Non empty text resource must always end with a newline + if ( + details.content.length !== 0 && + details.content.endsWith('\n') === false + ) { + details.content += '\n'; + } + } catch(ex) { + details = ex; + } + + // We want to return the caller's URL, not our internal one which may + // differ from the caller's one. + details.url = url; + + return details; +}; + +/******************************************************************************/ + +// https://github.com/gorhill/uBlock/issues/3331 +// Support the seamless loading of sublists. + +assets.fetchFilterList = async function(mainlistURL) { + const toParsedURL = url => { + try { + return new URL(url.trim()); + } catch (ex) { + } + }; + + // https://github.com/NanoAdblocker/NanoCore/issues/239 + // Anything under URL's root directory is allowed to be fetched. The + // URL of a sublist will always be relative to the URL of the parent + // list (instead of the URL of the root list). + let rootDirectoryURL = toParsedURL( + reIsExternalPath.test(mainlistURL) + ? mainlistURL + : vAPI.getURL(mainlistURL) + ); + if ( rootDirectoryURL !== undefined ) { + const pos = rootDirectoryURL.pathname.lastIndexOf('/'); + if ( pos !== -1 ) { + rootDirectoryURL.pathname = + rootDirectoryURL.pathname.slice(0, pos + 1); + } else { + rootDirectoryURL = undefined; + } + } + + const sublistURLs = new Set(); + + // https://github.com/uBlockOrigin/uBlock-issues/issues/1113 + // Process only `!#include` directives which are not excluded by an + // `!#if` directive. + const processIncludeDirectives = function(results) { + const out = []; + const reInclude = /^!#include +(\S+)[^\n\r]*(?:[\n\r]+|$)/gm; + for ( const result of results ) { + if ( typeof result === 'string' ) { + out.push(result); + continue; + } + if ( result instanceof Object === false ) { continue; } + const content = result.content; + const slices = µb.preparseDirectives.split(content); + for ( let i = 0, n = slices.length - 1; i < n; i++ ) { + const slice = content.slice(slices[i+0], slices[i+1]); + if ( (i & 1) !== 0 ) { + out.push(slice); + continue; + } + let lastIndex = 0; + for (;;) { + if ( rootDirectoryURL === undefined ) { break; } + const match = reInclude.exec(slice); + if ( match === null ) { break; } + if ( toParsedURL(match[1]) !== undefined ) { continue; } + if ( match[1].indexOf('..') !== -1 ) { continue; } + // Compute nested list path relative to parent list path + const pos = result.url.lastIndexOf('/'); + if ( pos === -1 ) { continue; } + const subURL = result.url.slice(0, pos + 1) + match[1].trim(); + if ( sublistURLs.has(subURL) ) { continue; } + sublistURLs.add(subURL); + out.push( + slice.slice(lastIndex, match.index + match[0].length), + `! >>>>>>>> ${subURL}\n`, + assets.fetchText(subURL), + `! <<<<<<<< ${subURL}\n` + ); + lastIndex = reInclude.lastIndex; + } + out.push(lastIndex === 0 ? slice : slice.slice(lastIndex)); + } + } + return out; + }; + + // https://github.com/AdguardTeam/FiltersRegistry/issues/82 + // Not checking for `errored` status was causing repeated notifications + // to the caller. This can happen when more than one out of multiple + // sublists can't be fetched. + + let allParts = [ + this.fetchText(mainlistURL) + ]; + // Abort processing `include` directives if at least one included sublist + // can't be fetched. + do { + allParts = await Promise.all(allParts); + const part = allParts.find(part => { + return typeof part === 'object' && part.error !== undefined; + }); + if ( part !== undefined ) { + return { url: mainlistURL, content: '', error: part.error }; + } + allParts = processIncludeDirectives(allParts); + } while ( allParts.some(part => typeof part !== 'string') ); + // If we reach this point, this means all fetches were successful. + return { + url: mainlistURL, + content: allParts.length === 1 + ? allParts[0] + : allParts.join('') + '\n' + }; +}; + +/******************************************************************************* + + The purpose of the asset source registry is to keep key detail information + about an asset: + - Where to load it from: this may consist of one or more URLs, either local + or remote. + - After how many days an asset should be deemed obsolete -- i.e. in need of + an update. + - The origin and type of an asset. + - The last time an asset was registered. + +**/ + +let assetSourceRegistryPromise; +let assetSourceRegistry = Object.create(null); + +const getAssetSourceRegistry = function() { + if ( assetSourceRegistryPromise === undefined ) { + assetSourceRegistryPromise = cacheStorage.get( + 'assetSourceRegistry' + ).then(bin => { + if ( + bin instanceof Object && + bin.assetSourceRegistry instanceof Object + ) { + assetSourceRegistry = bin.assetSourceRegistry; + return assetSourceRegistry; + } + return assets.fetchText( + µb.assetsBootstrapLocation || 'assets/assets.json' + ).then(details => { + return details.content !== '' + ? details + : assets.fetchText('assets/assets.json'); + }).then(details => { + updateAssetSourceRegistry(details.content, true); + return assetSourceRegistry; + }); + }); + } + + return assetSourceRegistryPromise; +}; + +const registerAssetSource = function(assetKey, dict) { + const entry = assetSourceRegistry[assetKey] || {}; + for ( const prop in dict ) { + if ( dict.hasOwnProperty(prop) === false ) { continue; } + if ( dict[prop] === undefined ) { + delete entry[prop]; + } else { + entry[prop] = dict[prop]; + } + } + let contentURL = dict.contentURL; + if ( contentURL !== undefined ) { + if ( typeof contentURL === 'string' ) { + contentURL = entry.contentURL = [ contentURL ]; + } else if ( Array.isArray(contentURL) === false ) { + contentURL = entry.contentURL = []; + } + let remoteURLCount = 0; + for ( let i = 0; i < contentURL.length; i++ ) { + if ( reIsExternalPath.test(contentURL[i]) ) { + remoteURLCount += 1; + } + } + entry.hasLocalURL = remoteURLCount !== contentURL.length; + entry.hasRemoteURL = remoteURLCount !== 0; + } else if ( entry.contentURL === undefined ) { + entry.contentURL = []; + } + if ( typeof entry.updateAfter !== 'number' ) { + entry.updateAfter = 5; + } + if ( entry.submitter ) { + entry.submitTime = Date.now(); // To detect stale entries + } + assetSourceRegistry[assetKey] = entry; +}; + +const unregisterAssetSource = function(assetKey) { + assetCacheRemove(assetKey); + delete assetSourceRegistry[assetKey]; +}; + +const saveAssetSourceRegistry = (( ) => { + let timer; + const save = function() { + timer = undefined; + cacheStorage.set({ assetSourceRegistry }); + }; + return function(lazily) { + if ( timer !== undefined ) { + clearTimeout(timer); + } + if ( lazily ) { + timer = vAPI.setTimeout(save, 500); + } else { + save(); + } + }; +})(); + +const updateAssetSourceRegistry = function(json, silent = false) { + let newDict; + try { + newDict = JSON.parse(json); + } catch (ex) { + } + if ( newDict instanceof Object === false ) { return; } + + const oldDict = assetSourceRegistry; + + // Remove obsolete entries (only those which were built-in). + for ( const assetKey in oldDict ) { + if ( + newDict[assetKey] === undefined && + oldDict[assetKey].submitter === undefined + ) { + unregisterAssetSource(assetKey); + } + } + // Add/update existing entries. Notify of new asset sources. + for ( const assetKey in newDict ) { + if ( oldDict[assetKey] === undefined && !silent ) { + fireNotification( + 'builtin-asset-source-added', + { assetKey: assetKey, entry: newDict[assetKey] } + ); + } + registerAssetSource(assetKey, newDict[assetKey]); + } + saveAssetSourceRegistry(); +}; + +assets.registerAssetSource = async function(assetKey, details) { + await getAssetSourceRegistry(); + registerAssetSource(assetKey, details); + saveAssetSourceRegistry(true); +}; + +assets.unregisterAssetSource = async function(assetKey) { + await getAssetSourceRegistry(); + unregisterAssetSource(assetKey); + saveAssetSourceRegistry(true); +}; + +/******************************************************************************* + + The purpose of the asset cache registry is to keep track of all assets + which have been persisted into the local cache. + +**/ + +const assetCacheRegistryStartTime = Date.now(); +let assetCacheRegistryPromise; +let assetCacheRegistry = {}; + +const getAssetCacheRegistry = function() { + if ( assetCacheRegistryPromise === undefined ) { + assetCacheRegistryPromise = cacheStorage.get( + 'assetCacheRegistry' + ).then(bin => { + if ( + bin instanceof Object && + bin.assetCacheRegistry instanceof Object + ) { + if ( Object.keys(assetCacheRegistry).length === 0 ) { + assetCacheRegistry = bin.assetCacheRegistry; + } else { + console.error( + 'getAssetCacheRegistry(): assetCacheRegistry reassigned!' + ); + if ( + Object.keys(bin.assetCacheRegistry).sort().join() !== + Object.keys(assetCacheRegistry).sort().join() + ) { + console.error( + 'getAssetCacheRegistry(): assetCacheRegistry changes overwritten!' + ); + } + } + } + return assetCacheRegistry; + }); + } + + return assetCacheRegistryPromise; +}; + +const saveAssetCacheRegistry = (( ) => { + let timer; + const save = function() { + timer = undefined; + cacheStorage.set({ assetCacheRegistry }); + }; + return function(lazily) { + if ( timer !== undefined ) { clearTimeout(timer); } + if ( lazily ) { + timer = vAPI.setTimeout(save, 30000); + } else { + save(); + } + }; +})(); + +const assetCacheRead = async function(assetKey, updateReadTime = false) { + const t0 = Date.now(); + const internalKey = `cache/${assetKey}`; + + const reportBack = function(content) { + if ( content instanceof Blob ) { content = ''; } + const details = { assetKey: assetKey, content: content }; + if ( content === '' ) { details.error = 'ENOTFOUND'; } + return details; + }; + + const [ , bin ] = await Promise.all([ + getAssetCacheRegistry(), + cacheStorage.get(internalKey), + ]); + + µb.supportStats.maxAssetCacheWait = Math.max( + Date.now() - t0, + parseInt(µb.supportStats.maxAssetCacheWait, 10) + ) + ' ms'; + + if ( + bin instanceof Object === false || + bin.hasOwnProperty(internalKey) === false + ) { + return reportBack(''); + } + + const entry = assetCacheRegistry[assetKey]; + if ( entry === undefined ) { + return reportBack(''); + } + + entry.readTime = Date.now(); + if ( updateReadTime ) { + saveAssetCacheRegistry(true); + } + + return reportBack(bin[internalKey]); +}; + +const assetCacheWrite = async function(assetKey, details) { + let content = ''; + let options = {}; + if ( typeof details === 'string' ) { + content = details; + } else if ( details instanceof Object ) { + content = details.content || ''; + options = details; + } + + if ( content === '' ) { + return assetCacheRemove(assetKey); + } + + const cacheDict = await getAssetCacheRegistry(); + + let entry = cacheDict[assetKey]; + if ( entry === undefined ) { + entry = cacheDict[assetKey] = {}; + } + entry.writeTime = entry.readTime = Date.now(); + if ( typeof options.url === 'string' ) { + entry.remoteURL = options.url; + } + cacheStorage.set({ + assetCacheRegistry, + [`cache/${assetKey}`]: content + }); + + const result = { assetKey, content }; + // https://github.com/uBlockOrigin/uBlock-issues/issues/248 + if ( options.silent !== true ) { + fireNotification('after-asset-updated', result); + } + return result; +}; + +const assetCacheRemove = async function(pattern) { + const cacheDict = await getAssetCacheRegistry(); + const removedEntries = []; + const removedContent = []; + for ( const assetKey in cacheDict ) { + if ( pattern instanceof RegExp && !pattern.test(assetKey) ) { + continue; + } + if ( typeof pattern === 'string' && assetKey !== pattern ) { + continue; + } + removedEntries.push(assetKey); + removedContent.push('cache/' + assetKey); + delete cacheDict[assetKey]; + } + if ( removedContent.length !== 0 ) { + await Promise.all([ + cacheStorage.remove(removedContent), + cacheStorage.set({ assetCacheRegistry }), + ]); + } + for ( let i = 0; i < removedEntries.length; i++ ) { + fireNotification('after-asset-updated', { + assetKey: removedEntries[i] + }); + } +}; + +const assetCacheMarkAsDirty = async function(pattern, exclude) { + const cacheDict = await getAssetCacheRegistry(); + let mustSave = false; + for ( const assetKey in cacheDict ) { + if ( pattern instanceof RegExp ) { + if ( pattern.test(assetKey) === false ) { continue; } + } else if ( typeof pattern === 'string' ) { + if ( assetKey !== pattern ) { continue; } + } else if ( Array.isArray(pattern) ) { + if ( pattern.indexOf(assetKey) === -1 ) { continue; } + } + if ( exclude instanceof RegExp ) { + if ( exclude.test(assetKey) ) { continue; } + } else if ( typeof exclude === 'string' ) { + if ( assetKey === exclude ) { continue; } + } else if ( Array.isArray(exclude) ) { + if ( exclude.indexOf(assetKey) !== -1 ) { continue; } + } + const cacheEntry = cacheDict[assetKey]; + if ( !cacheEntry.writeTime ) { continue; } + cacheDict[assetKey].writeTime = 0; + mustSave = true; + } + if ( mustSave ) { + cacheStorage.set({ assetCacheRegistry }); + } +}; + +/******************************************************************************/ + +const stringIsNotEmpty = function(s) { + return typeof s === 'string' && s !== ''; +}; + +/******************************************************************************* + + User assets are NOT persisted in the cache storage. User assets are + recognized by the asset key which always starts with 'user-'. + + TODO(seamless migration): + Can remove instances of old user asset keys when I am confident all users + are using uBO v1.11 and beyond. + +**/ + +/******************************************************************************* + + User assets are NOT persisted in the cache storage. User assets are + recognized by the asset key which always starts with 'user-'. + +**/ + +const readUserAsset = async function(assetKey) { + const bin = await vAPI.storage.get(assetKey); + const content = + bin instanceof Object && typeof bin[assetKey] === 'string' + ? bin[assetKey] + : ''; + + // Remove obsolete entry + // TODO: remove once everybody is well beyond 1.18.6 + vAPI.storage.remove('assets/user/filters.txt'); + + return { assetKey, content }; +}; + +const saveUserAsset = function(assetKey, content) { + return vAPI.storage.set({ [assetKey]: content }).then(( ) => { + return { assetKey, content }; + }); +}; + +/******************************************************************************/ + +assets.get = async function(assetKey, options = {}) { + if ( assetKey === µb.userFiltersPath ) { + return readUserAsset(assetKey); + } + + let assetDetails = {}; + + const reportBack = (content, url = '', err = undefined) => { + const details = { assetKey, content }; + if ( err !== undefined ) { + details.error = assetDetails.lastError = err; + } else { + assetDetails.lastError = undefined; + } + if ( options.needSourceURL ) { + if ( + url === '' && + assetCacheRegistry instanceof Object && + assetCacheRegistry[assetKey] instanceof Object + ) { + details.sourceURL = assetCacheRegistry[assetKey].remoteURL; + } + if ( reIsExternalPath.test(url) ) { + details.sourceURL = url; + } + } + return details; + }; + + // Skip read-time property for non-updatable assets: the property is + // completely unused for such assets and thus there is no point incurring + // storage write overhead at launch when reading compiled or selfie assets. + const updateReadTime = /^(?:compiled|selfie)\//.test(assetKey) === false; + + const details = await assetCacheRead(assetKey, updateReadTime); + if ( details.content !== '' ) { + return reportBack(details.content); + } + + const assetRegistry = await getAssetSourceRegistry(); + assetDetails = assetRegistry[assetKey] || {}; + const contentURLs = []; + if ( typeof assetDetails.contentURL === 'string' ) { + contentURLs.push(assetDetails.contentURL); + } else if ( Array.isArray(assetDetails.contentURL) ) { + contentURLs.push(...assetDetails.contentURL); + } else if ( reIsExternalPath.test(assetKey) ) { + assetDetails.content = 'filters'; + contentURLs.push(assetKey); + } + + // https://github.com/uBlockOrigin/uBlock-issues/issues/1566#issuecomment-826473517 + // Use CDN URLs as fall back URLs. + if ( Array.isArray(assetDetails.cdnURLs) ) { + contentURLs.push(...assetDetails.cdnURLs); + } + + for ( const contentURL of contentURLs ) { + if ( reIsExternalPath.test(contentURL) && assetDetails.hasLocalURL ) { + continue; + } + const details = assetDetails.content === 'filters' + ? await assets.fetchFilterList(contentURL) + : await assets.fetchText(contentURL); + if ( details.content === '' ) { continue; } + if ( reIsExternalPath.test(contentURL) && options.dontCache !== true ) { + assetCacheWrite(assetKey, { + content: details.content, + url: contentURL, + silent: options.silent === true, + }); + } + return reportBack(details.content, contentURL); + } + return reportBack('', '', 'ENOTFOUND'); +}; + +/******************************************************************************/ + +const getRemote = async function(assetKey) { + const assetRegistry = await getAssetSourceRegistry(); + const assetDetails = assetRegistry[assetKey] || {}; + + const reportBack = function(content, err) { + const details = { assetKey: assetKey, content: content }; + if ( err ) { + details.error = assetDetails.lastError = err; + } else { + assetDetails.lastError = undefined; + } + return details; + }; + + const contentURLs = []; + if ( typeof assetDetails.contentURL === 'string' ) { + contentURLs.push(assetDetails.contentURL); + } else if ( Array.isArray(assetDetails.contentURL) ) { + contentURLs.push(...assetDetails.contentURL); + } + + // If asked to be gentle on remote servers, favour using dedicated CDN + // servers. If more than one CDN server is present, randomly shuffle the + // set of servers so as to spread the bandwidth burden. + // + // https://github.com/uBlockOrigin/uBlock-issues/issues/1566#issuecomment-826473517 + // In case of manual update, use CDNs URLs as fall back URLs. + if ( Array.isArray(assetDetails.cdnURLs) ) { + const cdnURLs = assetDetails.cdnURLs.slice(); + for ( let i = 0, n = cdnURLs.length; i < n; i++ ) { + const j = Math.floor(Math.random() * n); + if ( j === i ) { continue; } + [ cdnURLs[j], cdnURLs[i] ] = [ cdnURLs[i], cdnURLs[j] ]; + } + if ( remoteServerFriendly ) { + contentURLs.unshift(...cdnURLs); + } else { + contentURLs.push(...cdnURLs); + } + } + + for ( const contentURL of contentURLs ) { + if ( reIsExternalPath.test(contentURL) === false ) { continue; } + + const result = assetDetails.content === 'filters' + ? await assets.fetchFilterList(contentURL) + : await assets.fetchText(contentURL); + + // Failure + if ( stringIsNotEmpty(result.content) === false ) { + let error = result.statusText; + if ( result.statusCode === 0 ) { + error = 'network error'; + } + registerAssetSource(assetKey, { + error: { time: Date.now(), error } + }); + continue; + } + + // Success + assetCacheWrite(assetKey, { + content: result.content, + url: contentURL + }); + registerAssetSource(assetKey, { error: undefined }); + return reportBack(result.content); + } + + return reportBack('', 'ENOTFOUND'); +}; + +/******************************************************************************/ + +assets.put = async function(assetKey, content) { + return reIsUserAsset.test(assetKey) + ? await saveUserAsset(assetKey, content) + : await assetCacheWrite(assetKey, content); +}; + +/******************************************************************************/ + +assets.metadata = async function() { + await Promise.all([ + getAssetSourceRegistry(), + getAssetCacheRegistry(), + ]); + + const assetDict = JSON.parse(JSON.stringify(assetSourceRegistry)); + const cacheDict = assetCacheRegistry; + const now = Date.now(); + for ( const assetKey in assetDict ) { + const assetEntry = assetDict[assetKey]; + const cacheEntry = cacheDict[assetKey]; + if ( + assetEntry.content === 'filters' && + assetEntry.external !== true + ) { + assetEntry.isDefault = + assetEntry.off === undefined || + assetEntry.off === true && + µb.listMatchesEnvironment(assetEntry); + } + if ( cacheEntry ) { + assetEntry.cached = true; + assetEntry.writeTime = cacheEntry.writeTime; + const obsoleteAfter = + cacheEntry.writeTime + assetEntry.updateAfter * 86400000; + assetEntry.obsolete = obsoleteAfter < now; + assetEntry.remoteURL = cacheEntry.remoteURL; + } else if ( + assetEntry.contentURL && + assetEntry.contentURL.length !== 0 + ) { + assetEntry.writeTime = 0; + assetEntry.obsolete = true; + } + } + + return assetDict; +}; + +/******************************************************************************/ + +assets.purge = assetCacheMarkAsDirty; + +assets.remove = function(pattern) { + return assetCacheRemove(pattern); +}; + +assets.rmrf = function() { + return assetCacheRemove(/./); +}; + +/******************************************************************************/ + +// Asset updater area. +const updaterAssetDelayDefault = 120000; +const updaterUpdated = []; +const updaterFetched = new Set(); + +let updaterStatus; +let updaterTimer; +let updaterAssetDelay = updaterAssetDelayDefault; +let updaterAuto = false; + +const updateFirst = function() { + updaterStatus = 'updating'; + updaterFetched.clear(); + updaterUpdated.length = 0; + fireNotification('before-assets-updated'); + updateNext(); +}; + +const updateNext = async function() { + const [ assetDict, cacheDict ] = await Promise.all([ + getAssetSourceRegistry(), + getAssetCacheRegistry(), + ]); + + const now = Date.now(); + const toUpdate = []; + for ( const assetKey in assetDict ) { + const assetEntry = assetDict[assetKey]; + if ( assetEntry.hasRemoteURL !== true ) { continue; } + if ( updaterFetched.has(assetKey) ) { continue; } + const cacheEntry = cacheDict[assetKey]; + if ( + (cacheEntry instanceof Object) && + (cacheEntry.writeTime + assetEntry.updateAfter * 86400000) > now + ) { + continue; + } + if ( + fireNotification('before-asset-updated', { + assetKey, + type: assetEntry.content + }) === true + ) { + toUpdate.push(assetKey); + continue; + } + // This will remove a cached asset when it's no longer in use. + if ( cacheEntry && cacheEntry.readTime < assetCacheRegistryStartTime ) { + assetCacheRemove(assetKey); + } + } + if ( toUpdate.length === 0 ) { + return updateDone(); + } + // https://github.com/uBlockOrigin/uBlock-issues/issues/1165 + // Update most obsolete asset first. + toUpdate.sort((a, b) => { + const ta = cacheDict[a] !== undefined ? cacheDict[a].writeTime : 0; + const tb = cacheDict[b] !== undefined ? cacheDict[b].writeTime : 0; + return ta - tb; + }); + updaterFetched.add(toUpdate[0]); + + // In auto-update context, be gentle on remote servers. + remoteServerFriendly = updaterAuto; + + const result = await getRemote(toUpdate[0]); + + remoteServerFriendly = false; + + if ( result.content !== '' ) { + updaterUpdated.push(result.assetKey); + if ( result.assetKey === 'assets.json' ) { + updateAssetSourceRegistry(result.content); + } + } else { + fireNotification('asset-update-failed', { assetKey: result.assetKey }); + } + + vAPI.setTimeout(updateNext, updaterAssetDelay); +}; + +const updateDone = function() { + const assetKeys = updaterUpdated.slice(0); + updaterFetched.clear(); + updaterUpdated.length = 0; + updaterStatus = undefined; + updaterAssetDelay = updaterAssetDelayDefault; + fireNotification('after-assets-updated', { assetKeys: assetKeys }); +}; + +assets.updateStart = function(details) { + const oldUpdateDelay = updaterAssetDelay; + const newUpdateDelay = typeof details.delay === 'number' + ? details.delay + : updaterAssetDelayDefault; + updaterAssetDelay = Math.min(oldUpdateDelay, newUpdateDelay); + updaterAuto = details.auto === true; + if ( updaterStatus !== undefined ) { + if ( newUpdateDelay < oldUpdateDelay ) { + clearTimeout(updaterTimer); + updaterTimer = vAPI.setTimeout(updateNext, updaterAssetDelay); + } + return; + } + updateFirst(); +}; + +assets.updateStop = function() { + if ( updaterTimer ) { + clearTimeout(updaterTimer); + updaterTimer = undefined; + } + if ( updaterStatus !== undefined ) { + updateDone(); + } +}; + +assets.isUpdating = function() { + return updaterStatus === 'updating' && + updaterAssetDelay <= µb.hiddenSettings.manualUpdateAssetFetchPeriod; +}; + +/******************************************************************************/ + +export default assets; + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/background.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/background.js new file mode 100644 index 0000000..e53bb5a --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/background.js @@ -0,0 +1,364 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +'use strict'; + +/******************************************************************************/ + +import logger from './logger.js'; +import { FilteringContext } from './filtering-context.js'; + +import { + domainFromHostname, + hostnameFromURI, + originFromURI, +} from './uri-utils.js'; + +/******************************************************************************/ + +// Not all platforms may have properly declared vAPI.webextFlavor. + +if ( vAPI.webextFlavor === undefined ) { + vAPI.webextFlavor = { major: 0, soup: new Set([ 'ublock' ]) }; +} + +/******************************************************************************/ + +const hiddenSettingsDefault = { + allowGenericProceduralFilters: false, + assetFetchTimeout: 30, + autoCommentFilterTemplate: '{{date}} {{origin}}', + autoUpdateAssetFetchPeriod: 60, + autoUpdateDelayAfterLaunch: 105, + autoUpdatePeriod: 4, + benchmarkDatasetURL: 'unset', + blockingProfiles: '11111/#F00 11010/#C0F 11001/#00F 00001', + cacheStorageAPI: 'unset', + cacheStorageCompression: true, + cacheControlForFirefox1376932: 'no-cache, no-store, must-revalidate', + cloudStorageCompression: true, + cnameIgnoreList: 'unset', + cnameIgnore1stParty: true, + cnameIgnoreExceptions: true, + cnameIgnoreRootDocument: true, + cnameMaxTTL: 120, + cnameReplayFullURL: false, + cnameUncloak: true, + cnameUncloakProxied: false, + consoleLogLevel: 'unset', + debugScriptlets: false, + debugScriptletInjector: false, + disableWebAssembly: false, + extensionUpdateForceReload: false, + filterAuthorMode: false, + filterOnHeaders: false, + loggerPopupType: 'popup', + manualUpdateAssetFetchPeriod: 500, + modifyWebextFlavor: 'unset', + popupFontSize: 'unset', + popupPanelDisabledSections: 0, + popupPanelLockedSections: 0, + popupPanelHeightMode: 0, + requestJournalProcessPeriod: 1000, + selfieAfter: 2, + strictBlockingBypassDuration: 120, + uiPopupConfig: 'unset', + uiStyles: 'unset', + updateAssetBypassBrowserCache: false, + userResourcesLocation: 'unset', +}; + +const userSettingsDefault = { + advancedUserEnabled: false, + alwaysDetachLogger: true, + autoUpdate: true, + cloudStorageEnabled: false, + cnameUncloakEnabled: true, + collapseBlocked: true, + colorBlindFriendly: false, + contextMenuEnabled: true, + uiAccentCustom: false, + uiAccentCustom0: '#aca0f7', + uiTheme: 'auto', + externalLists: '', + firewallPaneMinimized: true, + hyperlinkAuditingDisabled: true, + ignoreGenericCosmeticFilters: vAPI.webextFlavor.soup.has('mobile'), + importedLists: [], + largeMediaSize: 50, + parseAllABPHideFilters: true, + popupPanelSections: 0b111, + prefetchingDisabled: true, + requestLogMaxEntries: 1000, + showIconBadge: true, + suspendUntilListsAreLoaded: vAPI.Net.canSuspend(), + tooltipsDisabled: false, + webrtcIPAddressHidden: false, +}; + +const dynamicFilteringDefault = [ + 'behind-the-scene * * noop', + 'behind-the-scene * image noop', + 'behind-the-scene * 3p noop', + 'behind-the-scene * inline-script noop', + 'behind-the-scene * 1p-script noop', + 'behind-the-scene * 3p-script noop', + 'behind-the-scene * 3p-frame noop', +]; + +const hostnameSwitchesDefault = [ + 'no-large-media: behind-the-scene false', +]; +// https://github.com/LiCybora/NanoDefenderFirefox/issues/196 +if ( vAPI.webextFlavor.soup.has('firefox') ) { + hostnameSwitchesDefault.push('no-csp-reports: * true'); +} + +const µBlock = { // jshint ignore:line + userSettingsDefault: userSettingsDefault, + userSettings: Object.assign({}, userSettingsDefault), + + hiddenSettingsDefault: hiddenSettingsDefault, + hiddenSettingsAdmin: {}, + hiddenSettings: Object.assign({}, hiddenSettingsDefault), + + dynamicFilteringDefault, + hostnameSwitchesDefault, + + noDashboard: false, + + // Features detection. + privacySettingsSupported: vAPI.browserSettings instanceof Object, + cloudStorageSupported: vAPI.cloud instanceof Object, + canFilterResponseData: typeof browser.webRequest.filterResponseData === 'function', + canInjectScriptletsNow: vAPI.webextFlavor.soup.has('chromium'), + + // https://github.com/chrisaljoudi/uBlock/issues/180 + // Whitelist directives need to be loaded once the PSL is available + netWhitelist: new Map(), + netWhitelistModifyTime: 0, + netWhitelistDefault: [ + 'about-scheme', + 'chrome-extension-scheme', + 'chrome-scheme', + 'edge-scheme', + 'moz-extension-scheme', + 'opera-scheme', + 'vivaldi-scheme', + 'wyciwyg-scheme', // Firefox's "What-You-Cache-Is-What-You-Get" + ], + + localSettings: { + blockedRequestCount: 0, + allowedRequestCount: 0, + }, + localSettingsLastModified: 0, + localSettingsLastSaved: 0, + + // Read-only + systemSettings: { + compiledMagic: 46, // Increase when compiled format changes + selfieMagic: 46, // Increase when selfie format changes + }, + + // https://github.com/uBlockOrigin/uBlock-issues/issues/759#issuecomment-546654501 + // The assumption is that cache storage state reflects whether + // compiled or selfie assets are available or not. The properties + // below is to no longer rely on this assumption -- though it's still + // not clear how the assumption could be wrong, and it's still not + // clear whether relying on those properties will really solve the + // issue. It's just an attempt at hardening. + compiledFormatChanged: false, + selfieIsInvalid: false, + + restoreBackupSettings: { + lastRestoreFile: '', + lastRestoreTime: 0, + lastBackupFile: '', + lastBackupTime: 0, + }, + + commandShortcuts: new Map(), + + // Allows to fully customize uBO's assets, typically set through admin + // settings. The content of 'assets.json' will also tell which filter + // lists to enable by default when uBO is first installed. + assetsBootstrapLocation: undefined, + + userFiltersPath: 'user-filters', + pslAssetKey: 'public_suffix_list.dat', + + selectedFilterLists: [], + availableFilterLists: {}, + badLists: new Map(), + + // https://github.com/uBlockOrigin/uBlock-issues/issues/974 + // This can be used to defer filtering decision-making. + readyToFilter: false, + + supportStats: { + allReadyAfter: '', + maxAssetCacheWait: '0 ms', + }, + + pageStores: new Map(), + pageStoresToken: 0, + + storageQuota: vAPI.storage.QUOTA_BYTES, + storageUsed: 0, + + noopFunc: function(){}, + + apiErrorCount: 0, + + maybeGoodPopup: { + tabId: 0, + url: '', + }, + + epickerArgs: { + eprom: null, + mouse: false, + target: '', + zap: false, + }, + + scriptlets: {}, + + cspNoInlineScript: "script-src 'unsafe-eval' * blob: data:", + cspNoScripting: 'script-src http: https:', + cspNoInlineFont: 'font-src *', + + liveBlockingProfiles: [], + blockingProfileColorCache: new Map(), + uiAccentStylesheet: '', +}; + +µBlock.domainFromHostname = domainFromHostname; +µBlock.hostnameFromURI = hostnameFromURI; + +µBlock.FilteringContext = class extends FilteringContext { + duplicate() { + return (new µBlock.FilteringContext(this)); + } + + fromTabId(tabId) { + const tabContext = µBlock.tabContextManager.mustLookup(tabId); + this.tabOrigin = tabContext.origin; + this.tabHostname = tabContext.rootHostname; + this.tabDomain = tabContext.rootDomain; + this.tabId = tabContext.tabId; + return this; + } + + // https://github.com/uBlockOrigin/uBlock-issues/issues/459 + // In case of a request for frame and if ever no context is specified, + // assume the origin of the context is the same as the request itself. + fromWebrequestDetails(details) { + const tabId = details.tabId; + this.type = details.type; + if ( this.itype === this.MAIN_FRAME && tabId > 0 ) { + µBlock.tabContextManager.push(tabId, details.url); + } + this.fromTabId(tabId); // Must be called AFTER tab context management + this.realm = ''; + this.id = details.requestId; + this.setURL(details.url); + this.aliasURL = details.aliasURL || undefined; + if ( this.itype !== this.SUB_FRAME ) { + this.docId = details.frameId; + this.frameId = -1; + } else { + this.docId = details.parentFrameId; + this.frameId = details.frameId; + } + if ( this.tabId > 0 ) { + if ( this.docId === 0 ) { + this.docOrigin = this.tabOrigin; + this.docHostname = this.tabHostname; + this.docDomain = this.tabDomain; + } else if ( details.documentUrl !== undefined ) { + this.setDocOriginFromURL(details.documentUrl); + } else { + const pageStore = µBlock.pageStoreFromTabId(this.tabId); + const docStore = pageStore && pageStore.getFrameStore(this.docId); + if ( docStore ) { + this.setDocOriginFromURL(docStore.rawURL); + } else { + this.setDocOrigin(this.tabOrigin); + } + } + } else if ( details.documentUrl !== undefined ) { + const origin = originFromURI( + µBlock.normalizeTabURL(0, details.documentUrl) + ); + this.setDocOrigin(origin).setTabOrigin(origin); + } else if ( this.docId === -1 || (this.itype & this.FRAME_ANY) !== 0 ) { + const origin = originFromURI(this.url); + this.setDocOrigin(origin).setTabOrigin(origin); + } else { + this.setDocOrigin(this.tabOrigin); + } + this.redirectURL = undefined; + this.filter = undefined; + return this; + } + + getTabOrigin() { + if ( this.tabOrigin === undefined ) { + const tabContext = µBlock.tabContextManager.mustLookup(this.tabId); + this.tabOrigin = tabContext.origin; + this.tabHostname = tabContext.rootHostname; + this.tabDomain = tabContext.rootDomain; + } + return super.getTabOrigin(); + } + + toLogger() { + this.tstamp = Date.now(); + if ( this.domain === undefined ) { + void this.getDomain(); + } + if ( this.docDomain === undefined ) { + void this.getDocDomain(); + } + if ( this.tabDomain === undefined ) { + void this.getTabDomain(); + } + const filters = this.filter; + // Many filters may have been applied to the current context + if ( Array.isArray(filters) === false ) { + return logger.writeOne(this); + } + for ( const filter of filters ) { + this.filter = filter; + logger.writeOne(this); + } + } +}; + +µBlock.filteringContext = new µBlock.FilteringContext(); + +self.µBlock = µBlock; + +/******************************************************************************/ + +export default µBlock; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/base64-custom.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/base64-custom.js new file mode 100644 index 0000000..fab6c08 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/base64-custom.js @@ -0,0 +1,246 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +'use strict'; + +/******************************************************************************/ + +// Custom base64 codecs. These codecs are meant to encode/decode typed arrays +// to/from strings. + +// https://github.com/uBlockOrigin/uBlock-issues/issues/461 +// Provide a fallback encoding for Chromium 59 and less by issuing a plain +// JSON string. The fallback can be removed once min supported version is +// above 59. + +// TODO: rename µBlock.base64 to µBlock.SparseBase64, now that +// µBlock.DenseBase64 has been introduced. +// TODO: Should no longer need to test presence of TextEncoder/TextDecoder. + +const valToDigit = new Uint8Array(64); +const digitToVal = new Uint8Array(128); +{ + const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@%'; + for ( let i = 0, n = chars.length; i < n; i++ ) { + const c = chars.charCodeAt(i); + valToDigit[i] = c; + digitToVal[c] = i; + } +} + +// The sparse base64 codec is best for buffers which contains a lot of +// small u32 integer values. Those small u32 integer values are better +// represented with stringified integers, because small values can be +// represented with fewer bits than the usual base64 codec. For example, +// 0 become '0 ', i.e. 16 bits instead of 48 bits with official base64 +// codec. + +const sparseBase64 = { + magic: 'Base64_1', + + encode: function(arrbuf, arrlen) { + const inputLength = (arrlen + 3) >>> 2; + const inbuf = new Uint32Array(arrbuf, 0, inputLength); + const outputLength = this.magic.length + 7 + inputLength * 7; + const outbuf = new Uint8Array(outputLength); + // magic bytes + let j = 0; + for ( let i = 0; i < this.magic.length; i++ ) { + outbuf[j++] = this.magic.charCodeAt(i); + } + // array size + let v = inputLength; + do { + outbuf[j++] = valToDigit[v & 0b111111]; + v >>>= 6; + } while ( v !== 0 ); + outbuf[j++] = 0x20 /* ' ' */; + // array content + for ( let i = 0; i < inputLength; i++ ) { + v = inbuf[i]; + do { + outbuf[j++] = valToDigit[v & 0b111111]; + v >>>= 6; + } while ( v !== 0 ); + outbuf[j++] = 0x20 /* ' ' */; + } + if ( typeof TextDecoder === 'undefined' ) { + return JSON.stringify( + Array.from(new Uint32Array(outbuf.buffer, 0, j >>> 2)) + ); + } + const textDecoder = new TextDecoder(); + return textDecoder.decode(new Uint8Array(outbuf.buffer, 0, j)); + }, + + decode: function(instr, arrbuf) { + if ( instr.charCodeAt(0) === 0x5B /* '[' */ ) { + const inbuf = JSON.parse(instr); + if ( arrbuf instanceof ArrayBuffer === false ) { + return new Uint32Array(inbuf); + } + const outbuf = new Uint32Array(arrbuf); + outbuf.set(inbuf); + return outbuf; + } + if ( instr.startsWith(this.magic) === false ) { + throw new Error('Invalid µBlock.base64 encoding'); + } + const inputLength = instr.length; + const outputLength = this.decodeSize(instr) >> 2; + const outbuf = arrbuf instanceof ArrayBuffer === false + ? new Uint32Array(outputLength) + : new Uint32Array(arrbuf); + let i = instr.indexOf(' ', this.magic.length) + 1; + if ( i === -1 ) { + throw new Error('Invalid µBlock.base64 encoding'); + } + // array content + let j = 0; + for (;;) { + if ( j === outputLength || i >= inputLength ) { break; } + let v = 0, l = 0; + for (;;) { + const c = instr.charCodeAt(i++); + if ( c === 0x20 /* ' ' */ ) { break; } + v += digitToVal[c] << l; + l += 6; + } + outbuf[j++] = v; + } + if ( i < inputLength || j < outputLength ) { + throw new Error('Invalid µBlock.base64 encoding'); + } + return outbuf; + }, + + decodeSize: function(instr) { + if ( instr.startsWith(this.magic) === false ) { return 0; } + let v = 0, l = 0, i = this.magic.length; + for (;;) { + const c = instr.charCodeAt(i++); + if ( c === 0x20 /* ' ' */ ) { break; } + v += digitToVal[c] << l; + l += 6; + } + return v << 2; + }, +}; + +// The dense base64 codec is best for typed buffers which values are +// more random. For example, buffer contents as a result of compression +// contain less repetitive values and thus the content is more +// random-looking. + +// TODO: Investigate that in Firefox, creating a new Uint8Array from the +// ArrayBuffer fails, the content of the resulting Uint8Array is +// non-sensical. WASM-related? + +const denseBase64 = { + magic: 'DenseBase64_1', + + encode: function(input) { + const m = input.length % 3; + const n = input.length - m; + let outputLength = n / 3 * 4; + if ( m !== 0 ) { + outputLength += m + 1; + } + const output = new Uint8Array(outputLength); + let j = 0; + for ( let i = 0; i < n; i += 3) { + const i1 = input[i+0]; + const i2 = input[i+1]; + const i3 = input[i+2]; + output[j+0] = valToDigit[ i1 >>> 2]; + output[j+1] = valToDigit[i1 << 4 & 0b110000 | i2 >>> 4]; + output[j+2] = valToDigit[i2 << 2 & 0b111100 | i3 >>> 6]; + output[j+3] = valToDigit[i3 & 0b111111 ]; + j += 4; + } + if ( m !== 0 ) { + const i1 = input[n]; + output[j+0] = valToDigit[i1 >>> 2]; + if ( m === 1 ) { // 1 value + output[j+1] = valToDigit[i1 << 4 & 0b110000]; + } else { // 2 values + const i2 = input[n+1]; + output[j+1] = valToDigit[i1 << 4 & 0b110000 | i2 >>> 4]; + output[j+2] = valToDigit[i2 << 2 & 0b111100 ]; + } + } + const textDecoder = new TextDecoder(); + const b64str = textDecoder.decode(output); + return this.magic + b64str; + }, + + decode: function(instr, arrbuf) { + if ( instr.startsWith(this.magic) === false ) { + throw new Error('Invalid µBlock.denseBase64 encoding'); + } + const outputLength = this.decodeSize(instr); + const outbuf = arrbuf instanceof ArrayBuffer === false + ? new Uint8Array(outputLength) + : new Uint8Array(arrbuf); + const inputLength = instr.length - this.magic.length; + let i = this.magic.length; + let j = 0; + const m = inputLength & 3; + const n = i + inputLength - m; + while ( i < n ) { + const i1 = digitToVal[instr.charCodeAt(i+0)]; + const i2 = digitToVal[instr.charCodeAt(i+1)]; + const i3 = digitToVal[instr.charCodeAt(i+2)]; + const i4 = digitToVal[instr.charCodeAt(i+3)]; + i += 4; + outbuf[j+0] = i1 << 2 | i2 >>> 4; + outbuf[j+1] = i2 << 4 & 0b11110000 | i3 >>> 2; + outbuf[j+2] = i3 << 6 & 0b11000000 | i4; + j += 3; + } + if ( m !== 0 ) { + const i1 = digitToVal[instr.charCodeAt(i+0)]; + const i2 = digitToVal[instr.charCodeAt(i+1)]; + outbuf[j+0] = i1 << 2 | i2 >>> 4; + if ( m === 3 ) { + const i3 = digitToVal[instr.charCodeAt(i+2)]; + outbuf[j+1] = i2 << 4 & 0b11110000 | i3 >>> 2; + } + } + return outbuf; + }, + + decodeSize: function(instr) { + if ( instr.startsWith(this.magic) === false ) { return 0; } + const inputLength = instr.length - this.magic.length; + const m = inputLength & 3; + const n = inputLength - m; + let outputLength = (n >>> 2) * 3; + if ( m !== 0 ) { + outputLength += m - 1; + } + return outputLength; + }, +}; + +/******************************************************************************/ + +export { denseBase64, sparseBase64 }; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/benchmarks.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/benchmarks.js new file mode 100644 index 0000000..54a98fc --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/benchmarks.js @@ -0,0 +1,401 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +'use strict'; + +/******************************************************************************/ + +import cosmeticFilteringEngine from './cosmetic-filtering.js'; +import io from './assets.js'; +import scriptletFilteringEngine from './scriptlet-filtering.js'; +import staticNetFilteringEngine from './static-net-filtering.js'; +import µb from './background.js'; +import webRequest from './traffic.js'; +import { FilteringContext } from './filtering-context.js'; +import { LineIterator } from './text-utils.js'; +import { sessionFirewall } from './filtering-engines.js'; + +import { + domainFromHostname, + entityFromDomain, + hostnameFromURI, +} from './uri-utils.js'; + +/******************************************************************************/ + +// The requests.json.gz file can be downloaded from: +// https://cdn.cliqz.com/adblocking/requests_top500.json.gz +// +// Which is linked from: +// https://whotracks.me/blog/adblockers_performance_study.html +// +// Copy the file into ./tmp/requests.json.gz +// +// If the file is present when you build uBO using `make-[target].sh` from +// the shell, the resulting package will have `./assets/requests.json`, which +// will be looked-up by the method below to launch a benchmark session. +// +// From uBO's dev console, launch the benchmark: +// µBlock.staticNetFilteringEngine.benchmark(); +// +// The usual browser dev tools can be used to obtain useful profiling +// data, i.e. start the profiler, call the benchmark method from the +// console, then stop the profiler when it completes. +// +// Keep in mind that the measurements at the blog post above where obtained +// with ONLY EasyList. The CPU reportedly used was: +// https://www.cpubenchmark.net/cpu.php?cpu=Intel+Core+i7-6600U+%40+2.60GHz&id=2608 +// +// Rename ./tmp/requests.json.gz to something else if you no longer want +// ./assets/requests.json in the build. + +const loadBenchmarkDataset = (( ) => { + let datasetPromise; + let ttlTimer; + + return function() { + if ( ttlTimer !== undefined ) { + clearTimeout(ttlTimer); + ttlTimer = undefined; + } + + setTimeout(( ) => { + ttlTimer = undefined; + datasetPromise = undefined; + }, 5 * 60 * 1000); + + if ( datasetPromise !== undefined ) { + return datasetPromise; + } + + const datasetURL = µb.hiddenSettings.benchmarkDatasetURL; + if ( datasetURL === 'unset' ) { + console.info(`No benchmark dataset available.`); + return Promise.resolve(); + } + console.info(`Loading benchmark dataset...`); + datasetPromise = io.fetchText(datasetURL).then(details => { + console.info(`Parsing benchmark dataset...`); + let requests = []; + if ( details.content.startsWith('[') ) { + try { + requests = JSON.parse(details.content); + } catch(ex) { + } + } else { + const lineIter = new LineIterator(details.content); + const parsed = []; + while ( lineIter.eot() === false ) { + const line = lineIter.next().trim(); + if ( line === '' ) { continue; } + try { + parsed.push(JSON.parse(line)); + } catch(ex) { + parsed.length = 0; + break; + } + } + requests = parsed; + } + if ( requests.length === 0 ) { return; } + const out = []; + for ( const request of requests ) { + if ( request instanceof Object === false ) { continue; } + if ( !request.frameUrl || !request.url ) { continue; } + if ( request.cpt === 'document' ) { + request.cpt = 'main_frame'; + } else if ( request.cpt === 'xhr' ) { + request.cpt = 'xmlhttprequest'; + } + out.push(request); + } + return out; + }).catch(details => { + console.info(`Not found: ${details.url}`); + datasetPromise = undefined; + }); + + return datasetPromise; + }; +})(); + +/******************************************************************************/ + +// action: 1=test + +µb.benchmarkStaticNetFiltering = async function(options = {}) { + const { target, redirectEngine } = options; + + const requests = await loadBenchmarkDataset(); + if ( Array.isArray(requests) === false || requests.length === 0 ) { + const text = 'No dataset found to benchmark'; + console.info(text); + return text; + } + + console.info(`Benchmarking staticNetFilteringEngine.matchRequest()...`); + + const fctxt = new FilteringContext(); + + if ( typeof target === 'number' ) { + const request = requests[target]; + fctxt.setURL(request.url); + fctxt.setDocOriginFromURL(request.frameUrl); + fctxt.setType(request.cpt); + const r = staticNetFilteringEngine.matchRequest(fctxt); + console.info(`Result=${r}:`); + console.info(`\ttype=${fctxt.type}`); + console.info(`\turl=${fctxt.url}`); + console.info(`\tdocOrigin=${fctxt.getDocOrigin()}`); + if ( r !== 0 ) { + console.info(staticNetFilteringEngine.toLogData()); + } + return; + } + + const t0 = performance.now(); + let matchCount = 0; + let blockCount = 0; + let allowCount = 0; + for ( let i = 0; i < requests.length; i++ ) { + const request = requests[i]; + fctxt.setURL(request.url); + fctxt.setDocOriginFromURL(request.frameUrl); + fctxt.setType(request.cpt); + staticNetFilteringEngine.redirectURL = undefined; + const r = staticNetFilteringEngine.matchRequest(fctxt); + matchCount += 1; + if ( r === 1 ) { blockCount += 1; } + else if ( r === 2 ) { allowCount += 1; } + if ( r !== 1 ) { + if ( staticNetFilteringEngine.hasQuery(fctxt) ) { + staticNetFilteringEngine.filterQuery(fctxt, 'removeparam'); + } + if ( fctxt.type === 'main_frame' || fctxt.type === 'sub_frame' ) { + staticNetFilteringEngine.matchAndFetchModifiers(fctxt, 'csp'); + } + staticNetFilteringEngine.matchHeaders(fctxt, []); + } else if ( redirectEngine !== undefined ) { + staticNetFilteringEngine.redirectRequest(redirectEngine, fctxt); + } + } + const t1 = performance.now(); + const dur = t1 - t0; + + const output = [ + 'Benchmarked static network filtering engine:', + `\tEvaluated ${matchCount} match calls in ${dur.toFixed(0)} ms`, + `\tAverage: ${(dur / matchCount).toFixed(3)} ms per request`, + `\tNot blocked: ${matchCount - blockCount - allowCount}`, + `\tBlocked: ${blockCount}`, + `\tUnblocked: ${allowCount}`, + ]; + const s = output.join('\n'); + console.info(s); + return s; +}; + +/******************************************************************************/ + +µb.tokenHistograms = async function() { + const requests = await loadBenchmarkDataset(); + if ( Array.isArray(requests) === false || requests.length === 0 ) { + console.info('No requests found to benchmark'); + return; + } + + console.info(`Computing token histograms...`); + + const fctxt = new FilteringContext(); + const missTokenMap = new Map(); + const hitTokenMap = new Map(); + const reTokens = /[0-9a-z%]{2,}/g; + + for ( let i = 0; i < requests.length; i++ ) { + const request = requests[i]; + fctxt.setURL(request.url); + fctxt.setDocOriginFromURL(request.frameUrl); + fctxt.setType(request.cpt); + const r = staticNetFilteringEngine.matchRequest(fctxt); + for ( let [ keyword ] of request.url.toLowerCase().matchAll(reTokens) ) { + const token = keyword.slice(0, 7); + if ( r === 0 ) { + missTokenMap.set(token, (missTokenMap.get(token) || 0) + 1); + } else if ( r === 1 ) { + hitTokenMap.set(token, (hitTokenMap.get(token) || 0) + 1); + } + } + } + const customSort = (a, b) => b[1] - a[1]; + const topmisses = Array.from(missTokenMap).sort(customSort).slice(0, 100); + for ( const [ token ] of topmisses ) { + hitTokenMap.delete(token); + } + const tophits = Array.from(hitTokenMap).sort(customSort).slice(0, 100); + console.info('Misses:', JSON.stringify(topmisses)); + console.info('Hits:', JSON.stringify(tophits)); +}; + +/******************************************************************************/ + +µb.benchmarkDynamicNetFiltering = async function() { + const requests = await loadBenchmarkDataset(); + if ( Array.isArray(requests) === false || requests.length === 0 ) { + console.info('No requests found to benchmark'); + return; + } + console.info(`Benchmarking sessionFirewall.evaluateCellZY()...`); + const fctxt = new FilteringContext(); + const t0 = performance.now(); + for ( const request of requests ) { + fctxt.setURL(request.url); + fctxt.setTabOriginFromURL(request.frameUrl); + fctxt.setType(request.cpt); + sessionFirewall.evaluateCellZY( + fctxt.getTabHostname(), + fctxt.getHostname(), + fctxt.type + ); + } + const t1 = performance.now(); + const dur = t1 - t0; + console.info(`Evaluated ${requests.length} requests in ${dur.toFixed(0)} ms`); + console.info(`\tAverage: ${(dur / requests.length).toFixed(3)} ms per request`); +}; + +/******************************************************************************/ + +µb.benchmarkCosmeticFiltering = async function() { + const requests = await loadBenchmarkDataset(); + if ( Array.isArray(requests) === false || requests.length === 0 ) { + console.info('No requests found to benchmark'); + return; + } + console.info('Benchmarking cosmeticFilteringEngine.retrieveSpecificSelectors()...'); + const details = { + tabId: undefined, + frameId: undefined, + hostname: '', + domain: '', + entity: '', + }; + const options = { + noSpecificCosmeticFiltering: false, + noGenericCosmeticFiltering: false, + }; + let count = 0; + const t0 = performance.now(); + for ( let i = 0; i < requests.length; i++ ) { + const request = requests[i]; + if ( request.cpt !== 'main_frame' ) { continue; } + count += 1; + details.hostname = hostnameFromURI(request.url); + details.domain = domainFromHostname(details.hostname); + details.entity = entityFromDomain(details.domain); + void cosmeticFilteringEngine.retrieveSpecificSelectors(details, options); + } + const t1 = performance.now(); + const dur = t1 - t0; + console.info(`Evaluated ${count} requests in ${dur.toFixed(0)} ms`); + console.info(`\tAverage: ${(dur / count).toFixed(3)} ms per request`); +}; + +/******************************************************************************/ + +µb.benchmarkScriptletFiltering = async function() { + const requests = await loadBenchmarkDataset(); + if ( Array.isArray(requests) === false || requests.length === 0 ) { + console.info('No requests found to benchmark'); + return; + } + console.info('Benchmarking scriptletFilteringEngine.retrieve()...'); + const details = { + domain: '', + entity: '', + hostname: '', + tabId: 0, + url: '', + }; + let count = 0; + const t0 = performance.now(); + for ( let i = 0; i < requests.length; i++ ) { + const request = requests[i]; + if ( request.cpt !== 'main_frame' ) { continue; } + count += 1; + details.url = request.url; + details.hostname = hostnameFromURI(request.url); + details.domain = domainFromHostname(details.hostname); + details.entity = entityFromDomain(details.domain); + void scriptletFilteringEngine.retrieve(details); + } + const t1 = performance.now(); + const dur = t1 - t0; + console.info(`Evaluated ${count} requests in ${dur.toFixed(0)} ms`); + console.info(`\tAverage: ${(dur / count).toFixed(3)} ms per request`); +}; + +/******************************************************************************/ + +µb.benchmarkOnBeforeRequest = async function() { + const requests = await loadBenchmarkDataset(); + if ( Array.isArray(requests) === false || requests.length === 0 ) { + console.info('No requests found to benchmark'); + return; + } + const mappedTypes = new Map([ + [ 'document', 'main_frame' ], + [ 'subdocument', 'sub_frame' ], + ]); + console.info('webRequest.onBeforeRequest()...'); + const t0 = self.performance.now(); + const promises = []; + const details = { + documentUrl: '', + tabId: -1, + parentFrameId: -1, + frameId: 0, + type: '', + url: '', + }; + for ( const request of requests ) { + details.documentUrl = request.frameUrl; + details.tabId = -1; + details.parentFrameId = -1; + details.frameId = 0; + details.type = mappedTypes.get(request.cpt) || request.cpt; + details.url = request.url; + if ( details.type === 'main_frame' ) { continue; } + promises.push(webRequest.onBeforeRequest(details)); + } + return Promise.all(promises).then(results => { + let blockCount = 0; + for ( const r of results ) { + if ( r !== undefined ) { blockCount += 1; } + } + const t1 = self.performance.now(); + const dur = t1 - t0; + console.info(`Evaluated ${requests.length} requests in ${dur.toFixed(0)} ms`); + console.info(`\tBlocked ${blockCount} requests`); + console.info(`\tAverage: ${(dur / requests.length).toFixed(3)} ms per request`); + }); +}; + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/biditrie.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/biditrie.js new file mode 100644 index 0000000..38b780f --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/biditrie.js @@ -0,0 +1,932 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +/* globals WebAssembly, vAPI */ + +'use strict'; + +/******************************************************************************* + + A BidiTrieContainer is mostly a large buffer in which distinct but related + tries are stored. The memory layout of the buffer is as follow: + + 0-2047: haystack section + 2048-2051: number of significant characters in the haystack + 2052-2055: offset to start of trie data section (=> trie0) + 2056-2059: offset to end of trie data section (=> trie1) + 2060-2063: offset to start of character data section (=> char0) + 2064-2067: offset to end of character data section (=> char1) + 2068: start of trie data section + + +--------------+ + Normal cell: | And | If "Segment info" matches: + (aka CELL) +--------------+ Goto "And" + | Or | Else + +--------------+ Goto "Or" + | Segment info | + +--------------+ + + +--------------+ + Boundary cell: | Right And | "Right And" and/or "Left And" + (aka BCELL) +--------------+ can be 0 in last-segment condition. + | Left And | + +--------------+ + | 0 | + +--------------+ + + Given following filters and assuming token is "ad" for all of them: + + -images/ad- + /google_ad. + /images_ad. + _images/ad. + + We get the following internal representation: + + +-----------+ +-----------+ +---+ + | |---->| |---->| 0 | + +-----------+ +-----------+ +---+ +-----------+ + | 0 | +--| | | |---->| 0 | + +-----------+ | +-----------+ +---+ +-----------+ + | ad | | | - | | 0 | | 0 | + +-----------+ | +-----------+ +---+ +-----------+ + | | -images/ | + | +-----------+ +---+ +-----------+ + +->| |---->| 0 | + +-----------+ +---+ +-----------+ +-----------+ + | 0 | | |---->| |---->| 0 | + +-----------+ +---+ +-----------+ +-----------+ + | . | | 0 | +--| | +--| | + +-----------+ +---+ | +-----------+ | +-----------+ + | | _ | | | /google | + | +-----------+ | +-----------+ + | | + | | +-----------+ + | +->| 0 | + | +-----------+ + | | 0 | + | +-----------+ + | | /images | + | +-----------+ + | + | +-----------+ + +->| 0 | + +-----------+ + | 0 | + +-----------+ + | _images/ | + +-----------+ + +*/ + +const PAGE_SIZE = 65536*2; +const HAYSTACK_START = 0; +const HAYSTACK_SIZE = 2048; // i32 / i8 +const HAYSTACK_SIZE_SLOT = HAYSTACK_SIZE >>> 2; // 512 / 2048 +const TRIE0_SLOT = HAYSTACK_SIZE_SLOT + 1; // 513 / 2052 +const TRIE1_SLOT = HAYSTACK_SIZE_SLOT + 2; // 514 / 2056 +const CHAR0_SLOT = HAYSTACK_SIZE_SLOT + 3; // 515 / 2060 +const CHAR1_SLOT = HAYSTACK_SIZE_SLOT + 4; // 516 / 2064 +const RESULT_L_SLOT = HAYSTACK_SIZE_SLOT + 5; // 517 / 2068 +const RESULT_R_SLOT = HAYSTACK_SIZE_SLOT + 6; // 518 / 2072 +const RESULT_IU_SLOT = HAYSTACK_SIZE_SLOT + 7; // 519 / 2076 +const TRIE0_START = HAYSTACK_SIZE_SLOT + 8 << 2; // 2080 + +const CELL_BYTE_LENGTH = 12; +const MIN_FREE_CELL_BYTE_LENGTH = CELL_BYTE_LENGTH * 8; + +const CELL_AND = 0; +const CELL_OR = 1; +const SEGMENT_INFO = 2; +const BCELL_NEXT_AND = 0; +const BCELL_ALT_AND = 1; +const BCELL_EXTRA = 2; +const BCELL_EXTRA_MAX = 0x00FFFFFF; + +const toSegmentInfo = (aL, l, r) => ((r - l) << 24) | (aL + l); +const roundToPageSize = v => (v + PAGE_SIZE-1) & ~(PAGE_SIZE-1); + + +class BidiTrieContainer { + + constructor(extraHandler) { + const len = PAGE_SIZE * 4; + this.buf8 = new Uint8Array(len); + this.buf32 = new Uint32Array(this.buf8.buffer); + this.buf32[TRIE0_SLOT] = TRIE0_START; + this.buf32[TRIE1_SLOT] = this.buf32[TRIE0_SLOT]; + this.buf32[CHAR0_SLOT] = len >>> 1; + this.buf32[CHAR1_SLOT] = this.buf32[CHAR0_SLOT]; + this.haystack = this.buf8.subarray( + HAYSTACK_START, + HAYSTACK_START + HAYSTACK_SIZE + ); + this.extraHandler = extraHandler; + this.textDecoder = null; + this.wasmMemory = null; + + this.lastStored = ''; + this.lastStoredLen = this.lastStoredIndex = 0; + } + + //-------------------------------------------------------------------------- + // Public methods + //-------------------------------------------------------------------------- + + get haystackLen() { + return this.buf32[HAYSTACK_SIZE_SLOT]; + } + + set haystackLen(v) { + this.buf32[HAYSTACK_SIZE_SLOT] = v; + } + + reset(details) { + if ( + details instanceof Object && + typeof details.byteLength === 'number' && + typeof details.char0 === 'number' + ) { + if ( details.byteLength > this.buf8.byteLength ) { + this.reallocateBuf(details.byteLength); + } + this.buf32[CHAR0_SLOT] = details.char0; + } + this.buf32[TRIE1_SLOT] = this.buf32[TRIE0_SLOT]; + this.buf32[CHAR1_SLOT] = this.buf32[CHAR0_SLOT]; + + this.lastStored = ''; + this.lastStoredLen = this.lastStoredIndex = 0; + } + + createTrie() { + // grow buffer if needed + if ( (this.buf32[CHAR0_SLOT] - this.buf32[TRIE1_SLOT]) < CELL_BYTE_LENGTH ) { + this.growBuf(CELL_BYTE_LENGTH, 0); + } + const iroot = this.buf32[TRIE1_SLOT] >>> 2; + this.buf32[TRIE1_SLOT] += CELL_BYTE_LENGTH; + this.buf32[iroot+CELL_OR] = 0; + this.buf32[iroot+CELL_AND] = 0; + this.buf32[iroot+SEGMENT_INFO] = 0; + return iroot; + } + + matches(icell, ai) { + const buf32 = this.buf32; + const buf8 = this.buf8; + const char0 = buf32[CHAR0_SLOT]; + const aR = buf32[HAYSTACK_SIZE_SLOT]; + let al = ai, x = 0, y = 0; + for (;;) { + x = buf8[al]; + al += 1; + // find matching segment + for (;;) { + y = buf32[icell+SEGMENT_INFO]; + let bl = char0 + (y & 0x00FFFFFF); + if ( buf8[bl] === x ) { + y = (y >>> 24) - 1; + if ( y !== 0 ) { + x = al + y; + if ( x > aR ) { return 0; } + for (;;) { + bl += 1; + if ( buf8[bl] !== buf8[al] ) { return 0; } + al += 1; + if ( al === x ) { break; } + } + } + break; + } + icell = buf32[icell+CELL_OR]; + if ( icell === 0 ) { return 0; } + } + // next segment + icell = buf32[icell+CELL_AND]; + x = buf32[icell+BCELL_EXTRA]; + if ( x <= BCELL_EXTRA_MAX ) { + if ( x !== 0 && this.matchesExtra(ai, al, x) !== 0 ) { + return 1; + } + x = buf32[icell+BCELL_ALT_AND]; + if ( x !== 0 && this.matchesLeft(x, ai, al) !== 0 ) { + return 1; + } + icell = buf32[icell+BCELL_NEXT_AND]; + if ( icell === 0 ) { return 0; } + } + if ( al === aR ) { return 0; } + } + return 0; // eslint-disable-line no-unreachable + } + + matchesLeft(icell, ar, r) { + const buf32 = this.buf32; + const buf8 = this.buf8; + const char0 = buf32[CHAR0_SLOT]; + let x = 0, y = 0; + for (;;) { + if ( ar === 0 ) { return 0; } + ar -= 1; + x = buf8[ar]; + // find first segment with a first-character match + for (;;) { + y = buf32[icell+SEGMENT_INFO]; + let br = char0 + (y & 0x00FFFFFF); + y = (y >>> 24) - 1; + br += y; + if ( buf8[br] === x ) { // all characters in segment must match + if ( y !== 0 ) { + x = ar - y; + if ( x < 0 ) { return 0; } + for (;;) { + ar -= 1; br -= 1; + if ( buf8[ar] !== buf8[br] ) { return 0; } + if ( ar === x ) { break; } + } + } + break; + } + icell = buf32[icell+CELL_OR]; + if ( icell === 0 ) { return 0; } + } + // next segment + icell = buf32[icell+CELL_AND]; + x = buf32[icell+BCELL_EXTRA]; + if ( x <= BCELL_EXTRA_MAX ) { + if ( x !== 0 && this.matchesExtra(ar, r, x) !== 0 ) { + return 1; + } + icell = buf32[icell+BCELL_NEXT_AND]; + if ( icell === 0 ) { return 0; } + } + } + return 0; // eslint-disable-line no-unreachable + } + + matchesExtra(l, r, ix) { + let iu = 0; + if ( ix !== 1 ) { + iu = this.extraHandler(l, r, ix); + if ( iu === 0 ) { return 0; } + } else { + iu = -1; + } + this.buf32[RESULT_IU_SLOT] = iu; + this.buf32[RESULT_L_SLOT] = l; + this.buf32[RESULT_R_SLOT] = r; + return 1; + } + + get $l() { return this.buf32[RESULT_L_SLOT] | 0; } + get $r() { return this.buf32[RESULT_R_SLOT] | 0; } + get $iu() { return this.buf32[RESULT_IU_SLOT] | 0; } + + add(iroot, aL0, n, pivot = 0) { + const aR = n; + if ( aR === 0 ) { return 0; } + // Grow buffer if needed. The characters are already in our character + // data buffer, so we do not need to grow character data buffer. + if ( + (this.buf32[CHAR0_SLOT] - this.buf32[TRIE1_SLOT]) < + MIN_FREE_CELL_BYTE_LENGTH + ) { + this.growBuf(MIN_FREE_CELL_BYTE_LENGTH, 0); + } + const buf32 = this.buf32; + const char0 = buf32[CHAR0_SLOT]; + let icell = iroot; + let aL = char0 + aL0; + // special case: first node in trie + if ( buf32[icell+SEGMENT_INFO] === 0 ) { + buf32[icell+SEGMENT_INFO] = toSegmentInfo(aL0, pivot, aR); + return this.addLeft(icell, aL0, pivot); + } + const buf8 = this.buf8; + let al = pivot; + let inext; + // find a matching cell: move down + for (;;) { + const binfo = buf32[icell+SEGMENT_INFO]; + // length of segment + const bR = binfo >>> 24; + // skip boundary cells + if ( bR === 0 ) { + icell = buf32[icell+BCELL_NEXT_AND]; + continue; + } + let bl = char0 + (binfo & 0x00FFFFFF); + // if first character is no match, move to next descendant + if ( buf8[bl] !== buf8[aL+al] ) { + inext = buf32[icell+CELL_OR]; + if ( inext === 0 ) { + inext = this.addCell(0, 0, toSegmentInfo(aL0, al, aR)); + buf32[icell+CELL_OR] = inext; + return this.addLeft(inext, aL0, pivot); + } + icell = inext; + continue; + } + // 1st character was tested + let bi = 1; + al += 1; + // find 1st mismatch in rest of segment + if ( bR !== 1 ) { + for (;;) { + if ( bi === bR ) { break; } + if ( al === aR ) { break; } + if ( buf8[bl+bi] !== buf8[aL+al] ) { break; } + bi += 1; + al += 1; + } + } + // all segment characters matched + if ( bi === bR ) { + // needle remainder: no + if ( al === aR ) { + return this.addLeft(icell, aL0, pivot); + } + // needle remainder: yes + inext = buf32[icell+CELL_AND]; + if ( buf32[inext+CELL_AND] !== 0 ) { + icell = inext; + continue; + } + // add needle remainder + icell = this.addCell(0, 0, toSegmentInfo(aL0, al, aR)); + buf32[inext+CELL_AND] = icell; + return this.addLeft(icell, aL0, pivot); + } + // some characters matched + // split current segment + bl -= char0; + buf32[icell+SEGMENT_INFO] = bi << 24 | bl; + inext = this.addCell( + buf32[icell+CELL_AND], 0, bR - bi << 24 | bl + bi + ); + buf32[icell+CELL_AND] = inext; + // needle remainder: no = need boundary cell + if ( al === aR ) { + return this.addLeft(icell, aL0, pivot); + } + // needle remainder: yes = need new cell for remaining characters + icell = this.addCell(0, 0, toSegmentInfo(aL0, al, aR)); + buf32[inext+CELL_OR] = icell; + return this.addLeft(icell, aL0, pivot); + } + } + + addLeft(icell, aL0, pivot) { + const buf32 = this.buf32; + const char0 = buf32[CHAR0_SLOT]; + let aL = aL0 + char0; + // fetch boundary cell + let iboundary = buf32[icell+CELL_AND]; + // add boundary cell if none exist + if ( + iboundary === 0 || + buf32[iboundary+SEGMENT_INFO] > BCELL_EXTRA_MAX + ) { + const inext = iboundary; + iboundary = this.allocateCell(); + buf32[icell+CELL_AND] = iboundary; + buf32[iboundary+BCELL_NEXT_AND] = inext; + if ( pivot === 0 ) { return iboundary; } + } + // shortest match with no extra conditions will always win + if ( buf32[iboundary+BCELL_EXTRA] === 1 ) { + return iboundary; + } + // bail out if no left segment + if ( pivot === 0 ) { return iboundary; } + // fetch root cell of left segment + icell = buf32[iboundary+BCELL_ALT_AND]; + if ( icell === 0 ) { + icell = this.allocateCell(); + buf32[iboundary+BCELL_ALT_AND] = icell; + } + // special case: first node in trie + if ( buf32[icell+SEGMENT_INFO] === 0 ) { + buf32[icell+SEGMENT_INFO] = toSegmentInfo(aL0, 0, pivot); + iboundary = this.allocateCell(); + buf32[icell+CELL_AND] = iboundary; + return iboundary; + } + const buf8 = this.buf8; + let ar = pivot, inext; + // find a matching cell: move down + for (;;) { + const binfo = buf32[icell+SEGMENT_INFO]; + // skip boundary cells + if ( binfo <= BCELL_EXTRA_MAX ) { + inext = buf32[icell+CELL_AND]; + if ( inext !== 0 ) { + icell = inext; + continue; + } + iboundary = this.allocateCell(); + buf32[icell+CELL_AND] = + this.addCell(iboundary, 0, toSegmentInfo(aL0, 0, ar)); + // TODO: boundary cell might be last + // add remainder + boundary cell + return iboundary; + } + const bL = char0 + (binfo & 0x00FFFFFF); + const bR = bL + (binfo >>> 24); + let br = bR; + // if first character is no match, move to next descendant + if ( buf8[br-1] !== buf8[aL+ar-1] ) { + inext = buf32[icell+CELL_OR]; + if ( inext === 0 ) { + iboundary = this.allocateCell(); + inext = this.addCell( + iboundary, 0, toSegmentInfo(aL0, 0, ar) + ); + buf32[icell+CELL_OR] = inext; + return iboundary; + } + icell = inext; + continue; + } + // 1st character was tested + br -= 1; + ar -= 1; + // find 1st mismatch in rest of segment + if ( br !== bL ) { + for (;;) { + if ( br === bL ) { break; } + if ( ar === 0 ) { break; } + if ( buf8[br-1] !== buf8[aL+ar-1] ) { break; } + br -= 1; + ar -= 1; + } + } + // all segment characters matched + // a: ...vvvvvvv + // b: vvvvvvv + if ( br === bL ) { + inext = buf32[icell+CELL_AND]; + // needle remainder: no + // a: vvvvvvv + // b: vvvvvvv + // r: 0 & vvvvvvv + if ( ar === 0 ) { + // boundary cell already present + if ( buf32[inext+BCELL_EXTRA] <= BCELL_EXTRA_MAX ) { + return inext; + } + // need boundary cell + iboundary = this.allocateCell(); + buf32[iboundary+CELL_AND] = inext; + buf32[icell+CELL_AND] = iboundary; + return iboundary; + } + // needle remainder: yes + // a: yyyyyyyvvvvvvv + // b: vvvvvvv + else { + if ( inext !== 0 ) { + icell = inext; + continue; + } + // TODO: we should never reach here because there will + // always be a boundary cell. + // eslint-disable-next-line no-debugger + debugger; // jshint ignore:line + // boundary cell + needle remainder + inext = this.addCell(0, 0, 0); + buf32[icell+CELL_AND] = inext; + buf32[inext+CELL_AND] = + this.addCell(0, 0, toSegmentInfo(aL0, 0, ar)); + } + } + // some segment characters matched + // a: ...vvvvvvv + // b: yyyyyyyvvvvvvv + else { + // split current cell + buf32[icell+SEGMENT_INFO] = (bR - br) << 24 | (br - char0); + inext = this.addCell( + buf32[icell+CELL_AND], + 0, + (br - bL) << 24 | (bL - char0) + ); + // needle remainder: no = need boundary cell + // a: vvvvvvv + // b: yyyyyyyvvvvvvv + // r: yyyyyyy & 0 & vvvvvvv + if ( ar === 0 ) { + iboundary = this.allocateCell(); + buf32[icell+CELL_AND] = iboundary; + buf32[iboundary+CELL_AND] = inext; + return iboundary; + } + // needle remainder: yes = need new cell for remaining + // characters + // a: wwwwvvvvvvv + // b: yyyyyyyvvvvvvv + // r: (0 & wwww | yyyyyyy) & vvvvvvv + else { + buf32[icell+CELL_AND] = inext; + iboundary = this.allocateCell(); + buf32[inext+CELL_OR] = this.addCell( + iboundary, 0, toSegmentInfo(aL0, 0, ar) + ); + return iboundary; + } + } + //debugger; // jshint ignore:line + } + } + + getExtra(iboundary) { + return this.buf32[iboundary+BCELL_EXTRA]; + } + + setExtra(iboundary, v) { + this.buf32[iboundary+BCELL_EXTRA] = v; + } + + optimize(shrink = false) { + if ( shrink ) { + this.shrinkBuf(); + } + return { + byteLength: this.buf8.byteLength, + char0: this.buf32[CHAR0_SLOT], + }; + } + + serialize(encoder) { + if ( encoder instanceof Object ) { + return encoder.encode( + this.buf32.buffer, + this.buf32[CHAR1_SLOT] + ); + } + return Array.from( + new Uint32Array( + this.buf32.buffer, + 0, + this.buf32[CHAR1_SLOT] + 3 >>> 2 + ) + ); + } + + unserialize(selfie, decoder) { + const shouldDecode = typeof selfie === 'string'; + let byteLength = shouldDecode + ? decoder.decodeSize(selfie) + : selfie.length << 2; + if ( byteLength === 0 ) { return false; } + this.reallocateBuf(byteLength); + if ( shouldDecode ) { + decoder.decode(selfie, this.buf8.buffer); + } else { + this.buf32.set(selfie); + } + return true; + } + + storeString(s) { + const n = s.length; + if ( n === this.lastStoredLen && s === this.lastStored ) { + return this.lastStoredIndex; + } + this.lastStored = s; + this.lastStoredLen = n; + if ( (this.buf8.length - this.buf32[CHAR1_SLOT]) < n ) { + this.growBuf(0, n); + } + const offset = this.buf32[CHAR1_SLOT]; + this.buf32[CHAR1_SLOT] = offset + n; + const buf8 = this.buf8; + for ( let i = 0; i < n; i++ ) { + buf8[offset+i] = s.charCodeAt(i); + } + return (this.lastStoredIndex = offset - this.buf32[CHAR0_SLOT]); + } + + extractString(i, n) { + if ( this.textDecoder === null ) { + this.textDecoder = new TextDecoder(); + } + const offset = this.buf32[CHAR0_SLOT] + i; + return this.textDecoder.decode( + this.buf8.subarray(offset, offset + n) + ); + } + + // WASMable. + startsWith(haystackLeft, haystackRight, needleLeft, needleLen) { + if ( haystackLeft < 0 || (haystackLeft + needleLen) > haystackRight ) { + return 0; + } + const charCodes = this.buf8; + needleLeft += this.buf32[CHAR0_SLOT]; + const needleRight = needleLeft + needleLen; + while ( charCodes[haystackLeft] === charCodes[needleLeft] ) { + needleLeft += 1; + if ( needleLeft === needleRight ) { return 1; } + haystackLeft += 1; + } + return 0; + } + + // Find the left-most instance of substring in main string + // WASMable. + indexOf(haystackLeft, haystackEnd, needleLeft, needleLen) { + if ( needleLen === 0 ) { return haystackLeft; } + haystackEnd -= needleLen; + if ( haystackEnd < haystackLeft ) { return -1; } + needleLeft += this.buf32[CHAR0_SLOT]; + const needleRight = needleLeft + needleLen; + const charCodes = this.buf8; + for (;;) { + let i = haystackLeft; + let j = needleLeft; + while ( charCodes[i] === charCodes[j] ) { + j += 1; + if ( j === needleRight ) { return haystackLeft; } + i += 1; + } + haystackLeft += 1; + if ( haystackLeft > haystackEnd ) { break; } + } + return -1; + } + + // Find the right-most instance of substring in main string. + // WASMable. + lastIndexOf(haystackBeg, haystackEnd, needleLeft, needleLen) { + if ( needleLen === 0 ) { return haystackBeg; } + let haystackLeft = haystackEnd - needleLen; + if ( haystackLeft < haystackBeg ) { return -1; } + needleLeft += this.buf32[CHAR0_SLOT]; + const needleRight = needleLeft + needleLen; + const charCodes = this.buf8; + for (;;) { + let i = haystackLeft; + let j = needleLeft; + while ( charCodes[i] === charCodes[j] ) { + j += 1; + if ( j === needleRight ) { return haystackLeft; } + i += 1; + } + if ( haystackLeft === haystackBeg ) { break; } + haystackLeft -= 1; + } + return -1; + } + + dumpTrie(iroot) { + for ( const s of this.trieIterator(iroot) ) { + console.log(s); + } + } + + trieIterator(iroot) { + return { + value: undefined, + done: false, + next() { + if ( this.icell === 0 ) { + if ( this.forks.length === 0 ) { + this.value = undefined; + this.done = true; + return this; + } + this.charPtr = this.forks.pop(); + this.icell = this.forks.pop(); + } + for (;;) { + const idown = this.container.buf32[this.icell+CELL_OR]; + if ( idown !== 0 ) { + this.forks.push(idown, this.charPtr); + } + const v = this.container.buf32[this.icell+SEGMENT_INFO]; + let i0 = this.container.buf32[CHAR0_SLOT] + (v & 0x00FFFFFF); + const i1 = i0 + (v >>> 24); + while ( i0 < i1 ) { + this.charBuf[this.charPtr] = this.container.buf8[i0]; + this.charPtr += 1; + i0 += 1; + } + this.icell = this.container.buf32[this.icell+CELL_AND]; + if ( this.icell === 0 ) { + return this.toPattern(); + } + if ( this.container.buf32[this.icell+SEGMENT_INFO] === 0 ) { + this.icell = this.container.buf32[this.icell+CELL_AND]; + return this.toPattern(); + } + } + }, + toPattern() { + this.value = this.textDecoder.decode( + new Uint8Array(this.charBuf.buffer, 0, this.charPtr) + ); + return this; + }, + container: this, + icell: iroot, + charBuf: new Uint8Array(256), + charPtr: 0, + forks: [], + textDecoder: new TextDecoder(), + [Symbol.iterator]() { return this; }, + }; + } + + async enableWASM(wasmModuleFetcher, path) { + if ( typeof WebAssembly !== 'object' ) { return false; } + if ( this.wasmMemory instanceof WebAssembly.Memory ) { return true; } + const module = await getWasmModule(wasmModuleFetcher, path); + if ( module instanceof WebAssembly.Module === false ) { return false; } + const memory = new WebAssembly.Memory({ + initial: roundToPageSize(this.buf8.length) >>> 16 + }); + const instance = await WebAssembly.instantiate(module, { + imports: { memory, extraHandler: this.extraHandler } + }); + if ( instance instanceof WebAssembly.Instance === false ) { + return false; + } + this.wasmMemory = memory; + const curPageCount = memory.buffer.byteLength >>> 16; + const newPageCount = roundToPageSize(this.buf8.byteLength) >>> 16; + if ( newPageCount > curPageCount ) { + memory.grow(newPageCount - curPageCount); + } + const buf8 = new Uint8Array(memory.buffer); + buf8.set(this.buf8); + this.buf8 = buf8; + this.buf32 = new Uint32Array(this.buf8.buffer); + this.haystack = this.buf8.subarray( + HAYSTACK_START, + HAYSTACK_START + HAYSTACK_SIZE + ); + this.matches = instance.exports.matches; + this.startsWith = instance.exports.startsWith; + this.indexOf = instance.exports.indexOf; + this.lastIndexOf = instance.exports.lastIndexOf; + return true; + } + + dumpInfo() { + return [ + `Buffer size (Uint8Array): ${this.buf32[CHAR1_SLOT].toLocaleString('en')}`, + `WASM: ${this.wasmMemory === null ? 'disabled' : 'enabled'}`, + ].join('\n'); + } + + //-------------------------------------------------------------------------- + // Private methods + //-------------------------------------------------------------------------- + + allocateCell() { + let icell = this.buf32[TRIE1_SLOT]; + this.buf32[TRIE1_SLOT] = icell + CELL_BYTE_LENGTH; + icell >>>= 2; + this.buf32[icell+0] = 0; + this.buf32[icell+1] = 0; + this.buf32[icell+2] = 0; + return icell; + } + + addCell(iand, ior, v) { + const icell = this.allocateCell(); + this.buf32[icell+CELL_AND] = iand; + this.buf32[icell+CELL_OR] = ior; + this.buf32[icell+SEGMENT_INFO] = v; + return icell; + } + + growBuf(trieGrow, charGrow) { + const char0 = Math.max( + roundToPageSize(this.buf32[TRIE1_SLOT] + trieGrow), + this.buf32[CHAR0_SLOT] + ); + const char1 = char0 + this.buf32[CHAR1_SLOT] - this.buf32[CHAR0_SLOT]; + const bufLen = Math.max( + roundToPageSize(char1 + charGrow), + this.buf8.length + ); + if ( bufLen > this.buf8.length ) { + this.reallocateBuf(bufLen); + } + if ( char0 !== this.buf32[CHAR0_SLOT] ) { + this.buf8.copyWithin( + char0, + this.buf32[CHAR0_SLOT], + this.buf32[CHAR1_SLOT] + ); + this.buf32[CHAR0_SLOT] = char0; + this.buf32[CHAR1_SLOT] = char1; + } + } + + shrinkBuf() { + const char0 = this.buf32[TRIE1_SLOT] + MIN_FREE_CELL_BYTE_LENGTH; + const char1 = char0 + this.buf32[CHAR1_SLOT] - this.buf32[CHAR0_SLOT]; + const bufLen = char1 + 256; + if ( char0 !== this.buf32[CHAR0_SLOT] ) { + this.buf8.copyWithin( + char0, + this.buf32[CHAR0_SLOT], + this.buf32[CHAR1_SLOT] + ); + this.buf32[CHAR0_SLOT] = char0; + this.buf32[CHAR1_SLOT] = char1; + } + if ( bufLen < this.buf8.length ) { + this.reallocateBuf(bufLen); + } + } + + reallocateBuf(newSize) { + newSize = roundToPageSize(newSize); + if ( newSize === this.buf8.length ) { return; } + if ( this.wasmMemory === null ) { + const newBuf = new Uint8Array(newSize); + newBuf.set( + newBuf.length < this.buf8.length + ? this.buf8.subarray(0, newBuf.length) + : this.buf8 + ); + this.buf8 = newBuf; + } else { + const growBy = + ((newSize + 0xFFFF) >>> 16) - (this.buf8.length >>> 16); + if ( growBy <= 0 ) { return; } + this.wasmMemory.grow(growBy); + this.buf8 = new Uint8Array(this.wasmMemory.buffer); + } + this.buf32 = new Uint32Array(this.buf8.buffer); + this.haystack = this.buf8.subarray( + HAYSTACK_START, + HAYSTACK_START + HAYSTACK_SIZE + ); + } +} + +/******************************************************************************/ + +// Code below is to attempt to load a WASM module which implements: +// +// - BidiTrieContainer.startsWith() +// +// The WASM module is entirely optional, the JS implementations will be +// used should the WASM module be unavailable for whatever reason. + +const getWasmModule = (( ) => { + let wasmModulePromise; + + return async function(wasmModuleFetcher, path) { + if ( wasmModulePromise instanceof Promise ) { + return wasmModulePromise; + } + + if ( typeof WebAssembly !== 'object' ) { return; } + + // Soft-dependency on vAPI so that the code here can be used outside of + // uBO (i.e. tests, benchmarks) + if ( typeof vAPI === 'object' && vAPI.canWASM !== true ) { return; } + + // The wasm module will work only if CPU is natively little-endian, + // as we use native uint32 array in our js code. + const uint32s = new Uint32Array(1); + const uint8s = new Uint8Array(uint32s.buffer); + uint32s[0] = 1; + if ( uint8s[0] !== 1 ) { return; } + + wasmModulePromise = wasmModuleFetcher(`${path}biditrie`).catch(reason => { + console.info(reason); + }); + + return wasmModulePromise; + }; +})(); + +/******************************************************************************/ + +export default BidiTrieContainer; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/cachestorage.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/cachestorage.js new file mode 100644 index 0000000..78a87e2 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/cachestorage.js @@ -0,0 +1,489 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2016-present The uBlock Origin authors + + 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 +*/ + +/* global IDBDatabase, indexedDB */ + +'use strict'; + +/******************************************************************************/ + +import lz4Codec from './lz4.js'; +import µb from './background.js'; +import webext from './webext.js'; + +/******************************************************************************/ + +// The code below has been originally manually imported from: +// Commit: https://github.com/nikrolls/uBlock-Edge/commit/d1538ea9bea89d507219d3219592382eee306134 +// Commit date: 29 October 2016 +// Commit author: https://github.com/nikrolls +// Commit message: "Implement cacheStorage using IndexedDB" + +// The original imported code has been subsequently modified as it was not +// compatible with Firefox. +// (a Promise thing, see https://github.com/dfahlander/Dexie.js/issues/317) +// Furthermore, code to migrate from browser.storage.local to vAPI.storage +// has been added, for seamless migration of cache-related entries into +// indexedDB. + +// https://bugzilla.mozilla.org/show_bug.cgi?id=1371255 +// Firefox-specific: we use indexedDB because browser.storage.local() has +// poor performance in Firefox. +// https://github.com/uBlockOrigin/uBlock-issues/issues/328 +// Use IndexedDB for Chromium as well, to take advantage of LZ4 +// compression. +// https://github.com/uBlockOrigin/uBlock-issues/issues/399 +// Revert Chromium support of IndexedDB, use advanced setting to force +// IndexedDB. +// https://github.com/uBlockOrigin/uBlock-issues/issues/409 +// Allow forcing the use of webext storage on Firefox. + +const STORAGE_NAME = 'uBlock0CacheStorage'; + +// Default to webext storage. +const storageLocal = webext.storage.local; + +const cacheStorage = { + name: 'browser.storage.local', + get: storageLocal.get.bind(storageLocal), + set: storageLocal.set.bind(storageLocal), + remove: storageLocal.remove.bind(storageLocal), + clear: storageLocal.clear.bind(storageLocal), + // Not all platforms support getBytesInUse + getBytesInUse: storageLocal.getBytesInUse + ? storageLocal.getBytesInUse.bind(storageLocal) + : undefined, + select: function(selectedBackend) { + let actualBackend = selectedBackend; + if ( actualBackend === undefined || actualBackend === 'unset' ) { + actualBackend = vAPI.webextFlavor.soup.has('firefox') + ? 'indexedDB' + : 'browser.storage.local'; + } + if ( actualBackend === 'indexedDB' ) { + return selectIDB().then(success => { + if ( success || selectedBackend === 'indexedDB' ) { + clearWebext(); + return 'indexedDB'; + } + clearIDB(); + return 'browser.storage.local'; + }); + } + if ( actualBackend === 'browser.storage.local' ) { + clearIDB(); + } + return Promise.resolve('browser.storage.local'); + + }, + error: undefined +}; + +// Reassign API entries to that of indexedDB-based ones +const selectIDB = async function() { + let db; + let dbPromise; + let dbTimer; + + const noopfn = function () { + }; + + const disconnect = function() { + if ( dbTimer !== undefined ) { + clearTimeout(dbTimer); + dbTimer = undefined; + } + if ( db instanceof IDBDatabase ) { + db.close(); + db = undefined; + } + }; + + const keepAlive = function() { + if ( dbTimer !== undefined ) { + clearTimeout(dbTimer); + } + dbTimer = vAPI.setTimeout( + ( ) => { + dbTimer = undefined; + disconnect(); + }, + Math.max( + µb.hiddenSettings.autoUpdateAssetFetchPeriod * 2 * 1000, + 180000 + ) + ); + }; + + // https://github.com/gorhill/uBlock/issues/3156 + // I have observed that no event was fired in Tor Browser 7.0.7 + + // medium security level after the request to open the database was + // created. When this occurs, I have also observed that the `error` + // property was already set, so this means uBO can detect here whether + // the database can be opened successfully. A try-catch block is + // necessary when reading the `error` property because we are not + // allowed to read this property outside of event handlers in newer + // implementation of IDBRequest (my understanding). + + const getDb = function() { + keepAlive(); + if ( db !== undefined ) { + return Promise.resolve(db); + } + if ( dbPromise !== undefined ) { + return dbPromise; + } + dbPromise = new Promise(resolve => { + let req; + try { + req = indexedDB.open(STORAGE_NAME, 1); + if ( req.error ) { + console.log(req.error); + req = undefined; + } + } catch(ex) { + } + if ( req === undefined ) { + db = null; + dbPromise = undefined; + return resolve(null); + } + req.onupgradeneeded = function(ev) { + if ( ev.oldVersion === 1 ) { return; } + try { + const db = ev.target.result; + db.createObjectStore(STORAGE_NAME, { keyPath: 'key' }); + } catch(ex) { + req.onerror(); + } + }; + req.onsuccess = function(ev) { + if ( resolve === undefined ) { return; } + req = undefined; + db = ev.target.result; + dbPromise = undefined; + resolve(db); + resolve = undefined; + }; + req.onerror = req.onblocked = function() { + if ( resolve === undefined ) { return; } + req = undefined; + console.log(this.error); + db = null; + dbPromise = undefined; + resolve(null); + resolve = undefined; + }; + setTimeout(( ) => { + if ( resolve === undefined ) { return; } + db = null; + dbPromise = undefined; + resolve(null); + resolve = undefined; + }, 5000); + }); + return dbPromise; + }; + + const fromBlob = function(data) { + if ( data instanceof Blob === false ) { + return Promise.resolve(data); + } + return new Promise(resolve => { + const blobReader = new FileReader(); + blobReader.onloadend = ev => { + resolve(new Uint8Array(ev.target.result)); + }; + blobReader.readAsArrayBuffer(data); + }); + }; + + const toBlob = function(data) { + const value = data instanceof Uint8Array + ? new Blob([ data ]) + : data; + return Promise.resolve(value); + }; + + const compress = function(store, key, data) { + return lz4Codec.encode(data, toBlob).then(value => { + store.push({ key, value }); + }); + }; + + const decompress = function(store, key, data) { + return lz4Codec.decode(data, fromBlob).then(data => { + store[key] = data; + }); + }; + + const getFromDb = async function(keys, keyvalStore, callback) { + if ( typeof callback !== 'function' ) { return; } + if ( keys.length === 0 ) { return callback(keyvalStore); } + const promises = []; + const gotOne = function() { + if ( typeof this.result !== 'object' ) { return; } + const { key, value } = this.result; + keyvalStore[key] = value; + if ( value instanceof Blob === false ) { return; } + promises.push(decompress(keyvalStore, key, value)); + }; + try { + const db = await getDb(); + if ( !db ) { return callback(); } + const transaction = db.transaction(STORAGE_NAME, 'readonly'); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = ( ) => { + Promise.all(promises).then(( ) => { + callback(keyvalStore); + }); + }; + const table = transaction.objectStore(STORAGE_NAME); + for ( const key of keys ) { + const req = table.get(key); + req.onsuccess = gotOne; + req.onerror = noopfn; + } + } + catch(reason) { + console.info(`cacheStorage.getFromDb() failed: ${reason}`); + callback(); + } + }; + + const visitAllFromDb = async function(visitFn) { + const db = await getDb(); + if ( !db ) { return visitFn(); } + const transaction = db.transaction(STORAGE_NAME, 'readonly'); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = ( ) => visitFn(); + const table = transaction.objectStore(STORAGE_NAME); + const req = table.openCursor(); + req.onsuccess = function(ev) { + let cursor = ev.target && ev.target.result; + if ( !cursor ) { return; } + let entry = cursor.value; + visitFn(entry); + cursor.continue(); + }; + }; + + const getAllFromDb = function(callback) { + if ( typeof callback !== 'function' ) { return; } + const promises = []; + const keyvalStore = {}; + visitAllFromDb(entry => { + if ( entry === undefined ) { + Promise.all(promises).then(( ) => { + callback(keyvalStore); + }); + return; + } + const { key, value } = entry; + keyvalStore[key] = value; + if ( entry.value instanceof Blob === false ) { return; } + promises.push(decompress(keyvalStore, key, value)); + }).catch(reason => { + console.info(`cacheStorage.getAllFromDb() failed: ${reason}`); + callback(); + }); + }; + + // https://github.com/uBlockOrigin/uBlock-issues/issues/141 + // Mind that IDBDatabase.transaction() and IDBObjectStore.put() + // can throw: + // https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/transaction + // https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/put + + const putToDb = async function(keyvalStore, callback) { + if ( typeof callback !== 'function' ) { + callback = noopfn; + } + const keys = Object.keys(keyvalStore); + if ( keys.length === 0 ) { return callback(); } + const promises = [ getDb() ]; + const entries = []; + const dontCompress = + µb.hiddenSettings.cacheStorageCompression !== true; + for ( const key of keys ) { + const value = keyvalStore[key]; + const isString = typeof value === 'string'; + if ( isString === false || dontCompress ) { + entries.push({ key, value }); + continue; + } + promises.push(compress(entries, key, value)); + } + const finish = ( ) => { + if ( callback === undefined ) { return; } + let cb = callback; + callback = undefined; + cb(); + }; + try { + const results = await Promise.all(promises); + const db = results[0]; + if ( !db ) { return callback(); } + const transaction = db.transaction( + STORAGE_NAME, + 'readwrite' + ); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = finish; + const table = transaction.objectStore(STORAGE_NAME); + for ( const entry of entries ) { + table.put(entry); + } + } catch (ex) { + finish(); + } + }; + + const deleteFromDb = async function(input, callback) { + if ( typeof callback !== 'function' ) { + callback = noopfn; + } + const keys = Array.isArray(input) ? input.slice() : [ input ]; + if ( keys.length === 0 ) { return callback(); } + const finish = ( ) => { + if ( callback === undefined ) { return; } + let cb = callback; + callback = undefined; + cb(); + }; + try { + const db = await getDb(); + if ( !db ) { return callback(); } + const transaction = db.transaction(STORAGE_NAME, 'readwrite'); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = finish; + const table = transaction.objectStore(STORAGE_NAME); + for ( const key of keys ) { + table.delete(key); + } + } catch (ex) { + finish(); + } + }; + + const clearDb = async function(callback) { + if ( typeof callback !== 'function' ) { + callback = noopfn; + } + try { + const db = await getDb(); + if ( !db ) { return callback(); } + const transaction = db.transaction(STORAGE_NAME, 'readwrite'); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = ( ) => { + callback(); + }; + transaction.objectStore(STORAGE_NAME).clear(); + } + catch(reason) { + console.info(`cacheStorage.clearDb() failed: ${reason}`); + callback(); + } + }; + + await getDb(); + if ( !db ) { return false; } + + cacheStorage.name = 'indexedDB'; + cacheStorage.get = function get(keys) { + return new Promise(resolve => { + if ( keys === null ) { + return getAllFromDb(bin => resolve(bin)); + } + let toRead, output = {}; + if ( typeof keys === 'string' ) { + toRead = [ keys ]; + } else if ( Array.isArray(keys) ) { + toRead = keys; + } else /* if ( typeof keys === 'object' ) */ { + toRead = Object.keys(keys); + output = keys; + } + getFromDb(toRead, output, bin => resolve(bin)); + }); + }; + cacheStorage.set = function set(keys) { + return new Promise(resolve => { + putToDb(keys, details => resolve(details)); + }); + }; + cacheStorage.remove = function remove(keys) { + return new Promise(resolve => { + deleteFromDb(keys, ( ) => resolve()); + }); + }; + cacheStorage.clear = function clear() { + return new Promise(resolve => { + clearDb(( ) => resolve()); + }); + }; + cacheStorage.getBytesInUse = function getBytesInUse() { + return Promise.resolve(0); + }; + return true; +}; + +// https://github.com/uBlockOrigin/uBlock-issues/issues/328 +// Delete cache-related entries from webext storage. +const clearWebext = async function() { + const bin = await webext.storage.local.get('assetCacheRegistry'); + if ( + bin instanceof Object === false || + bin.assetCacheRegistry instanceof Object === false + ) { + return; + } + const toRemove = [ + 'assetCacheRegistry', + 'assetSourceRegistry', + 'resourcesSelfie', + 'selfie' + ]; + for ( const key in bin.assetCacheRegistry ) { + if ( bin.assetCacheRegistry.hasOwnProperty(key) ) { + toRemove.push('cache/' + key); + } + } + webext.storage.local.remove(toRemove); +}; + +const clearIDB = function() { + try { + indexedDB.deleteDatabase(STORAGE_NAME); + } catch(ex) { + } +}; + +/******************************************************************************/ + +export default cacheStorage; + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/click2load.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/click2load.js new file mode 100644 index 0000000..2220392 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/click2load.js @@ -0,0 +1,60 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +'use strict'; + +/******************************************************************************/ +/******************************************************************************/ + +(( ) => { + +/******************************************************************************/ + +if ( typeof vAPI !== 'object' ) { return; } + +const url = new URL(self.location.href); +const actualURL = url.searchParams.get('url'); +const frameURL = url.searchParams.get('aliasURL') || actualURL; +const frameURLElem = document.getElementById('frameURL'); + +frameURLElem.children[0].textContent = actualURL; + +frameURLElem.children[1].href = frameURL; +frameURLElem.children[1].title = frameURL; + +document.body.setAttribute('title', actualURL); + +document.body.addEventListener('click', ev => { + if ( ev.isTrusted === false ) { return; } + if ( ev.target.closest('#frameURL') !== null ) { return; } + vAPI.messaging.send('default', { + what: 'clickToLoad', + frameURL, + }).then(ok => { + if ( ok ) { + self.location.replace(frameURL); + } + }); +}); + +/******************************************************************************/ + +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/cloud-ui.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/cloud-ui.js new file mode 100644 index 0000000..103cbc2 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/cloud-ui.js @@ -0,0 +1,237 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-2018 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 +*/ + +/* global uDom, faIconsInit */ + +'use strict'; + +/******************************************************************************/ + +(( ) => { + +/******************************************************************************/ + +self.cloud = { + options: {}, + datakey: '', + data: undefined, + onPush: null, + onPull: null, +}; + +/******************************************************************************/ + +const widget = uDom.nodeFromId('cloudWidget'); +if ( widget === null ) { return; } + +self.cloud.datakey = widget.getAttribute('data-cloud-entry') || ''; +if ( self.cloud.datakey === '' ) { return; } + +/******************************************************************************/ + +const fetchStorageUsed = async function() { + let elem = widget.querySelector('#cloudCapacity'); + if ( elem.classList.contains('hide') ) { return; } + const result = await vAPI.messaging.send('cloudWidget', { + what: 'cloudUsed', + datakey: self.cloud.datakey, + }); + if ( result instanceof Object === false ) { + elem.classList.add('hide'); + return; + } + const units = ' ' + vAPI.i18n('genericBytes'); + elem.title = result.max.toLocaleString() + units; + const total = (result.total / result.max * 100).toFixed(1); + elem = elem.firstElementChild; + elem.style.width = `${total}%`; + elem.title = result.total.toLocaleString() + units; + const used = (result.used / result.total * 100).toFixed(1); + elem = elem.firstElementChild; + elem.style.width = `${used}%`; + elem.title = result.used.toLocaleString() + units; +}; + +/******************************************************************************/ + +const fetchCloudData = async function() { + const info = widget.querySelector('#cloudInfo'); + + const entry = await vAPI.messaging.send('cloudWidget', { + what: 'cloudPull', + datakey: self.cloud.datakey, + }); + + const hasData = entry instanceof Object; + if ( hasData === false ) { + uDom.nodeFromId('cloudPull').setAttribute('disabled', ''); + uDom.nodeFromId('cloudPullAndMerge').setAttribute('disabled', ''); + info.textContent = '...\n...'; + return entry; + } + + self.cloud.data = entry.data; + + uDom.nodeFromId('cloudPull').removeAttribute('disabled'); + uDom.nodeFromId('cloudPullAndMerge').removeAttribute('disabled'); + + const timeOptions = { + weekday: 'short', + year: 'numeric', + month: 'short', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + timeZoneName: 'short' + }; + + const time = new Date(entry.tstamp); + info.textContent = + entry.source + '\n' + + time.toLocaleString('fullwide', timeOptions); +}; + +/******************************************************************************/ + +const pushData = async function() { + if ( typeof self.cloud.onPush !== 'function' ) { return; } + + const error = await vAPI.messaging.send('cloudWidget', { + what: 'cloudPush', + datakey: self.cloud.datakey, + data: self.cloud.onPush(), + }); + const failed = typeof error === 'string'; + document.getElementById('cloudPush') + .classList + .toggle('error', failed); + document.querySelector('#cloudError') + .textContent = failed ? error : ''; + if ( failed ) { return; } + fetchCloudData(); + fetchStorageUsed(); +}; + +/******************************************************************************/ + +const pullData = function() { + if ( typeof self.cloud.onPull === 'function' ) { + self.cloud.onPull(self.cloud.data, false); + } + document.getElementById('cloudPush').classList.remove('error'); + document.querySelector('#cloudError').textContent = ''; +}; + +/******************************************************************************/ + +const pullAndMergeData = function() { + if ( typeof self.cloud.onPull === 'function' ) { + self.cloud.onPull(self.cloud.data, true); + } +}; + +/******************************************************************************/ + +const openOptions = function() { + const input = uDom.nodeFromId('cloudDeviceName'); + input.value = self.cloud.options.deviceName; + input.setAttribute('placeholder', self.cloud.options.defaultDeviceName); + uDom.nodeFromId('cloudOptions').classList.add('show'); +}; + +/******************************************************************************/ + +const closeOptions = function(ev) { + const root = uDom.nodeFromId('cloudOptions'); + if ( ev.target !== root ) { return; } + root.classList.remove('show'); +}; + +/******************************************************************************/ + +const submitOptions = async function() { + uDom.nodeFromId('cloudOptions').classList.remove('show'); + + const options = await vAPI.messaging.send('cloudWidget', { + what: 'cloudSetOptions', + options: { + deviceName: uDom.nodeFromId('cloudDeviceName').value + }, + }); + if ( options instanceof Object ) { + self.cloud.options = options; + } +}; + +/******************************************************************************/ + +const onInitialize = function(options) { + if ( options instanceof Object === false ) { return; } + if ( options.enabled !== true ) { return; } + self.cloud.options = options; + + const xhr = new XMLHttpRequest(); + xhr.open('GET', 'cloud-ui.html', true); + xhr.overrideMimeType('text/html;charset=utf-8'); + xhr.responseType = 'text'; + xhr.onload = function() { + this.onload = null; + const parser = new DOMParser(), + parsed = parser.parseFromString(this.responseText, 'text/html'), + fromParent = parsed.body; + while ( fromParent.firstElementChild !== null ) { + widget.appendChild( + document.adoptNode(fromParent.firstElementChild) + ); + } + + faIconsInit(widget); + + vAPI.i18n.render(widget); + widget.classList.remove('hide'); + + uDom('#cloudPush').on('click', ( ) => { pushData(); }); + uDom('#cloudPull').on('click', pullData); + uDom('#cloudPullAndMerge').on('click', pullAndMergeData); + uDom('#cloudCog').on('click', openOptions); + uDom('#cloudOptions').on('click', closeOptions); + uDom('#cloudOptionsSubmit').on('click', ( ) => { submitOptions(); }); + + fetchCloudData().then(result => { + if ( typeof result !== 'string' ) { return; } + document.getElementById('cloudPush').classList.add('error'); + document.querySelector('#cloudError').textContent = result; + }); + fetchStorageUsed(); + }; + xhr.send(); +}; + +vAPI.messaging.send('cloudWidget', { + what: 'cloudGetOptions', +}).then(options => { + onInitialize(options); +}); + +/******************************************************************************/ + +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/codemirror/search-thread.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/codemirror/search-thread.js new file mode 100644 index 0000000..7b33fb1 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/codemirror/search-thread.js @@ -0,0 +1,203 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2020-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 +*/ + +'use strict'; + +/******************************************************************************/ + +(( ) => { +// >>>>> start of local scope + +/******************************************************************************/ + +// Worker context + +if ( + self.WorkerGlobalScope instanceof Object && + self instanceof self.WorkerGlobalScope +) { + let content = ''; + + const doSearch = function(details) { + const reEOLs = /\n\r|\r\n|\n|\r/g; + const t1 = Date.now() + 750; + + let reSearch; + try { + reSearch = new RegExp(details.pattern, details.flags); + } catch(ex) { + return; + } + + const response = []; + const maxOffset = content.length; + let iLine = 0; + let iOffset = 0; + let size = 0; + while ( iOffset < maxOffset ) { + // Find next match + const match = reSearch.exec(content); + if ( match === null ) { break; } + // Find number of line breaks between last and current match. + reEOLs.lastIndex = 0; + const eols = content.slice(iOffset, match.index).match(reEOLs); + if ( Array.isArray(eols) ) { + iLine += eols.length; + } + // Store line + response.push(iLine); + size += 1; + // Find next line break. + reEOLs.lastIndex = reSearch.lastIndex; + const eol = reEOLs.exec(content); + iOffset = eol !== null + ? reEOLs.lastIndex + : content.length; + reSearch.lastIndex = iOffset; + iLine += 1; + // Quit if this takes too long + if ( (size & 0x3FF) === 0 && Date.now() >= t1 ) { break; } + } + + return response; + }; + + self.onmessage = function(e) { + const msg = e.data; + + switch ( msg.what ) { + case 'setHaystack': + content = msg.content; + break; + + case 'doSearch': + const response = doSearch(msg); + self.postMessage({ id: msg.id, response }); + break; + } + }; + + return; +} + +/******************************************************************************/ + +// Main context + +{ + const workerTTL = 5 * 60 * 1000; + const pendingResponses = new Map(); + + let worker; + let workerTTLTimer; + let messageId = 1; + + const onWorkerMessage = function(e) { + const msg = e.data; + const resolver = pendingResponses.get(msg.id); + if ( resolver === undefined ) { return; } + pendingResponses.delete(msg.id); + resolver(msg.response); + }; + + const cancelPendingTasks = function() { + for ( const resolver of pendingResponses.values() ) { + resolver(); + } + pendingResponses.clear(); + }; + + const destroy = function() { + shutdown(); + self.searchThread = undefined; + }; + + const shutdown = function() { + if ( workerTTLTimer !== undefined ) { + clearTimeout(workerTTLTimer); + workerTTLTimer = undefined; + } + if ( worker === undefined ) { return; } + worker.terminate(); + worker.onmessage = undefined; + worker = undefined; + cancelPendingTasks(); + }; + + const init = function() { + if ( self.searchThread instanceof Object === false ) { return; } + if ( worker === undefined ) { + worker = new Worker('js/codemirror/search-thread.js'); + worker.onmessage = onWorkerMessage; + } + if ( workerTTLTimer !== undefined ) { + clearTimeout(workerTTLTimer); + } + workerTTLTimer = vAPI.setTimeout(shutdown, workerTTL); + }; + + const needHaystack = function() { + return worker instanceof Object === false; + }; + + const setHaystack = function(content) { + init(); + worker.postMessage({ what: 'setHaystack', content }); + }; + + const search = function(query, overwrite = true) { + init(); + if ( worker instanceof Object === false ) { + return Promise.resolve(); + } + if ( overwrite ) { + cancelPendingTasks(); + } + const id = messageId++; + worker.postMessage({ + what: 'doSearch', + id, + pattern: query.source, + flags: query.flags, + isRE: query instanceof RegExp + }); + return new Promise(resolve => { + pendingResponses.set(id, resolve); + }); + }; + + self.addEventListener( + 'beforeunload', + ( ) => { destroy(); }, + { once: true } + ); + + self.searchThread = { needHaystack, setHaystack, search, shutdown }; +} + +/******************************************************************************/ + +// <<<<< end of local scope +})(); + +/******************************************************************************/ + +void 0; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/codemirror/search.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/codemirror/search.js new file mode 100644 index 0000000..125bd28 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/codemirror/search.js @@ -0,0 +1,452 @@ +// The following code is heavily based on the standard CodeMirror +// search addon found at: https://codemirror.net/addon/search/search.js +// I added/removed and modified code in order to get a closer match to a +// browser's built-in find-in-page feature which are just enough for +// uBlock Origin. +// +// This file was originally wholly imported from: +// https://github.com/codemirror/CodeMirror/blob/3e1bb5fff682f8f6cbfaef0e56c61d62403d4798/addon/search/search.js +// +// And has been modified over time to better suit uBO's usage and coding style: +// https://github.com/gorhill/uBlock/commits/master/src/js/codemirror/search.js +// +// The original copyright notice is reproduced below: + +// ===== +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: http://codemirror.net/LICENSE + +// Define search commands. Depends on dialog.js or another +// implementation of the openDialog method. + +// Replace works a little oddly -- it will do the replace on the next +// Ctrl-G (or whatever is bound to findNext) press. You prevent a +// replace by making sure the match is no longer selected when hitting +// Ctrl-G. +// ===== + +'use strict'; + +(function(CodeMirror) { + + const searchOverlay = function(query, caseInsensitive) { + if ( typeof query === 'string' ) + query = new RegExp( + query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), + caseInsensitive ? 'gi' : 'g' + ); + else if ( !query.global ) + query = new RegExp(query.source, query.ignoreCase ? 'gi' : 'g'); + + return { + token: function(stream) { + query.lastIndex = stream.pos; + const match = query.exec(stream.string); + if ( match && match.index === stream.pos ) { + stream.pos += match[0].length || 1; + return 'searching'; + } else if ( match ) { + stream.pos = match.index; + } else { + stream.skipToEnd(); + } + } + }; + }; + + const searchWidgetKeydownHandler = function(cm, ev) { + const keyName = CodeMirror.keyName(ev); + if ( !keyName ) { return; } + CodeMirror.lookupKey( + keyName, + cm.getOption('keyMap'), + function(command) { + if ( widgetCommandHandler(cm, command) ) { + ev.preventDefault(); + ev.stopPropagation(); + } + } + ); + }; + + const searchWidgetInputHandler = function(cm) { + let state = getSearchState(cm); + if ( queryTextFromSearchWidget(cm) === state.queryText ) { return; } + if ( state.queryTimer !== null ) { + clearTimeout(state.queryTimer); + } + state.queryTimer = setTimeout( + () => { + state.queryTimer = null; + findCommit(cm, 0); + }, + 350 + ); + }; + + const searchWidgetClickHandler = function(cm, ev) { + const tcl = ev.target.classList; + if ( tcl.contains('cm-search-widget-up') ) { + findNext(cm, -1); + } else if ( tcl.contains('cm-search-widget-down') ) { + findNext(cm, 1); + } + if ( ev.target.localName !== 'input' ) { + ev.preventDefault(); + } else { + ev.stopImmediatePropagation(); + } + }; + + const queryTextFromSearchWidget = function(cm) { + return getSearchState(cm).widget.querySelector('input[type="search"]').value; + }; + + const queryTextToSearchWidget = function(cm, q) { + const input = getSearchState(cm).widget.querySelector('input[type="search"]'); + if ( typeof q === 'string' && q !== input.value ) { + input.value = q; + } + input.setSelectionRange(0, input.value.length); + input.focus(); + }; + + const SearchState = function(cm) { + this.query = null; + this.panel = null; + const widgetParent = document.querySelector('.cm-search-widget-template').cloneNode(true); + this.widget = widgetParent.children[0]; + this.widget.addEventListener('keydown', searchWidgetKeydownHandler.bind(null, cm)); + this.widget.addEventListener('input', searchWidgetInputHandler.bind(null, cm)); + this.widget.addEventListener('mousedown', searchWidgetClickHandler.bind(null, cm)); + if ( typeof cm.addPanel === 'function' ) { + this.panel = cm.addPanel(this.widget); + } + this.queryText = ''; + this.queryTimer = null; + this.dirty = true; + this.lines = []; + cm.on('changes', (cm, changes) => { + for ( const change of changes ) { + if ( change.text.length !== 0 || change.removed !== 0 ) { + this.dirty = true; + break; + } + } + }); + cm.on('cursorActivity', cm => { + updateCount(cm); + }); + }; + + // We want the search widget to behave as if the focus was on the + // CodeMirror editor. + + const reSearchCommands = /^(?:find|findNext|findPrev|newlineAndIndent)$/; + + const widgetCommandHandler = function(cm, command) { + if ( reSearchCommands.test(command) === false ) { return false; } + const queryText = queryTextFromSearchWidget(cm); + if ( command === 'find' ) { + queryTextToSearchWidget(cm); + return true; + } + if ( queryText.length !== 0 ) { + findNext(cm, command === 'findPrev' ? -1 : 1); + } + return true; + }; + + const getSearchState = function(cm) { + return cm.state.search || (cm.state.search = new SearchState(cm)); + }; + + const queryCaseInsensitive = function(query) { + return typeof query === 'string' && query === query.toLowerCase(); + }; + + // Heuristic: if the query string is all lowercase, do a case insensitive search. + const getSearchCursor = function(cm, query, pos) { + return cm.getSearchCursor( + query, + pos, + { caseFold: queryCaseInsensitive(query), multiline: false } + ); + }; + + // https://github.com/uBlockOrigin/uBlock-issues/issues/658 + // Modified to backslash-escape ONLY widely-used control characters. + const parseString = function(string) { + return string.replace(/\\[nrt\\]/g, match => { + if ( match === '\\n' ) { return '\n'; } + if ( match === '\\r' ) { return '\r'; } + if ( match === '\\t' ) { return '\t'; } + if ( match === '\\\\' ) { return '\\'; } + return match; + }); + }; + + const reEscape = /[.*+\-?^${}()|[\]\\]/g; + + // Must always return a RegExp object. + // + // Assume case-sensitivity if there is at least one uppercase in plain + // query text. + const parseQuery = function(query) { + let flags = 'i'; + let reParsed = query.match(/^\/(.+)\/([iu]*)$/); + if ( reParsed !== null ) { + try { + const re = new RegExp(reParsed[1], reParsed[2]); + query = re.source; + flags = re.flags; + } + catch (e) { + reParsed = null; + } + } + if ( reParsed === null ) { + if ( /[A-Z]/.test(query) ) { flags = ''; } + query = parseString(query).replace(reEscape, '\\$&'); + } + if ( typeof query === 'string' ? query === '' : query.test('') ) { + query = 'x^'; + } + return new RegExp(query, 'gm' + flags); + }; + + let intlNumberFormat; + + const formatNumber = function(n) { + if ( intlNumberFormat === undefined ) { + intlNumberFormat = null; + if ( Intl.NumberFormat instanceof Function ) { + const intl = new Intl.NumberFormat(undefined, { + notation: 'compact', + maximumSignificantDigits: 3 + }); + if ( + intl.resolvedOptions instanceof Function && + intl.resolvedOptions().hasOwnProperty('notation') + ) { + intlNumberFormat = intl; + } + } + } + return n > 10000 && intlNumberFormat instanceof Object + ? intlNumberFormat.format(n) + : n.toLocaleString(); + }; + + const updateCount = function(cm) { + const state = getSearchState(cm); + const lines = state.lines; + const current = cm.getCursor().line; + let l = 0; + let r = lines.length; + let i = -1; + while ( l < r ) { + i = l + r >>> 1; + const candidate = lines[i]; + if ( current === candidate ) { break; } + if ( current < candidate ) { + r = i; + } else /* if ( current > candidate ) */ { + l = i + 1; + } + } + let text = ''; + if ( i !== -1 ) { + text = formatNumber(i + 1); + if ( lines[i] !== current ) { + text = '~' + text; + } + text = text + '\xA0/\xA0'; + } + const count = lines.length; + text += formatNumber(count); + const span = state.widget.querySelector('.cm-search-widget-count'); + span.textContent = text; + span.title = count.toLocaleString(); + }; + + const startSearch = function(cm, state) { + state.query = parseQuery(state.queryText); + if ( state.overlay !== undefined ) { + cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query)); + } + state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query)); + cm.addOverlay(state.overlay); + if ( state.dirty || self.searchThread.needHaystack() ) { + self.searchThread.setHaystack(cm.getValue()); + state.dirty = false; + } + self.searchThread.search(state.query).then(lines => { + if ( Array.isArray(lines) === false ) { return; } + state.lines = lines; + const count = lines.length; + updateCount(cm); + if ( state.annotate !== undefined ) { + state.annotate.clear(); + state.annotate = undefined; + } + if ( count === 0 ) { return; } + state.annotate = cm.annotateScrollbar('CodeMirror-search-match'); + const annotations = []; + let lineBeg = -1; + let lineEnd = -1; + for ( const line of lines ) { + if ( lineBeg === -1 ) { + lineBeg = line; + lineEnd = line + 1; + continue; + } else if ( line === lineEnd ) { + lineEnd = line + 1; + continue; + } + annotations.push({ + from: { line: lineBeg, ch: 0 }, + to: { line: lineEnd, ch: 0 } + }); + lineBeg = -1; + } + if ( lineBeg !== -1 ) { + annotations.push({ + from: { line: lineBeg, ch: 0 }, + to: { line: lineEnd, ch: 0 } + }); + } + state.annotate.update(annotations); + }); + state.widget.setAttribute('data-query', state.queryText); + // Ensure the caret is visible + const input = state.widget.querySelector('.cm-search-widget-input input'); + input.selectionStart = input.selectionStart; + }; + + const findNext = function(cm, dir, callback) { + cm.operation(function() { + const state = getSearchState(cm); + if ( !state.query ) { return; } + let cursor = getSearchCursor( + cm, + state.query, + dir <= 0 ? cm.getCursor('from') : cm.getCursor('to') + ); + const previous = dir < 0; + if (!cursor.find(previous)) { + cursor = getSearchCursor( + cm, + state.query, + previous + ? CodeMirror.Pos(cm.lastLine()) + : CodeMirror.Pos(cm.firstLine(), 0) + ); + if (!cursor.find(previous)) return; + } + cm.setSelection(cursor.from(), cursor.to()); + const { clientHeight } = cm.getScrollInfo(); + cm.scrollIntoView( + { from: cursor.from(), to: cursor.to() }, + clientHeight >>> 1 + ); + if (callback) callback(cursor.from(), cursor.to()); + }); + }; + + const clearSearch = function(cm, hard) { + cm.operation(function() { + const state = getSearchState(cm); + if ( state.query ) { + state.query = state.queryText = null; + } + state.lines = []; + if ( state.overlay !== undefined ) { + cm.removeOverlay(state.overlay); + state.overlay = undefined; + } + if ( state.annotate ) { + state.annotate.clear(); + state.annotate = undefined; + } + state.widget.removeAttribute('data-query'); + if ( hard ) { + state.panel.clear(); + state.panel = null; + state.widget = null; + cm.state.search = null; + } + }); + }; + + const findCommit = function(cm, dir) { + const state = getSearchState(cm); + if ( state.queryTimer !== null ) { + clearTimeout(state.queryTimer); + state.queryTimer = null; + } + const queryText = queryTextFromSearchWidget(cm); + if ( queryText === state.queryText ) { return; } + state.queryText = queryText; + if ( state.queryText === '' ) { + clearSearch(cm); + } else { + cm.operation(function() { + startSearch(cm, state); + findNext(cm, dir); + }); + } + }; + + const findCommand = function(cm) { + let queryText = cm.getSelection() || undefined; + if ( !queryText ) { + const word = cm.findWordAt(cm.getCursor()); + queryText = cm.getRange(word.anchor, word.head); + if ( /^\W|\W$/.test(queryText) ) { + queryText = undefined; + } + cm.setCursor(word.anchor); + } + queryTextToSearchWidget(cm, queryText); + findCommit(cm, 1); + }; + + const findNextCommand = function(cm) { + const state = getSearchState(cm); + if ( state.query ) { return findNext(cm, 1); } + }; + + const findPrevCommand = function(cm) { + const state = getSearchState(cm); + if ( state.query ) { return findNext(cm, -1); } + }; + + { + const searchWidgetTemplate = + ''; + const domParser = new DOMParser(); + const doc = domParser.parseFromString(searchWidgetTemplate, 'text/html'); + const widgetTemplate = document.adoptNode(doc.body.firstElementChild); + document.body.appendChild(widgetTemplate); + } + + CodeMirror.commands.find = findCommand; + CodeMirror.commands.findNext = findNextCommand; + CodeMirror.commands.findPrev = findPrevCommand; + + CodeMirror.defineInitHook(function(cm) { + getSearchState(cm); + }); +})(self.CodeMirror); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/codemirror/ubo-dynamic-filtering.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/codemirror/ubo-dynamic-filtering.js new file mode 100644 index 0000000..8ce760c --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/codemirror/ubo-dynamic-filtering.js @@ -0,0 +1,239 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +/* global CodeMirror */ + +'use strict'; + +CodeMirror.defineMode('ubo-dynamic-filtering', ( ) => { + + const validSwitches = new Set([ + 'no-strict-blocking:', + 'no-popups:', + 'no-cosmetic-filtering:', + 'no-remote-fonts:', + 'no-large-media:', + 'no-csp-reports:', + 'no-scripting:', + ]); + const validSwitcheStates = new Set([ + 'true', + 'false', + ]); + const validHnRuleTypes = new Set([ + '*', + '3p', + 'image', + 'inline-script', + '1p-script', + '3p-script', + '3p-frame', + ]); + const invalidURLRuleTypes = new Set([ + 'doc', + 'main_frame', + ]); + const validActions = new Set([ + 'block', + 'allow', + 'noop', + ]); + const hnValidator = new URL(self.location.href); + const reBadHn = /[%]|^\.|\.$/; + const slices = []; + let sliceIndex = 0; + let sliceCount = 0; + let hostnameToDomainMap = new Map(); + let psl; + + const isValidHostname = hnin => { + if ( hnin === '*' ) { return true; } + hnValidator.hostname = '_'; + try { + hnValidator.hostname = hnin; + } catch(_) { + return false; + } + const hnout = hnValidator.hostname; + return hnout !== '_' && hnout !== '' && reBadHn.test(hnout) === false; + }; + + const addSlice = (len, style = null) => { + let i = sliceCount; + if ( i === slices.length ) { + slices[i] = { len: 0, style: null }; + } + const entry = slices[i]; + entry.len = len; + entry.style = style; + sliceCount += 1; + }; + + const addMatchSlice = (match, style = null) => { + const len = match !== null ? match[0].length : 0; + addSlice(len, style); + return match !== null ? match.input.slice(len) : ''; + }; + + const addMatchHnSlices = (match, style = null) => { + const hn = match[0]; + if ( hn === '*' ) { + return addMatchSlice(match, style); + } + let dn = hostnameToDomainMap.get(hn) || ''; + if ( dn === '' && psl !== undefined ) { + dn = /(\d|\])$/.test(hn) ? hn : (psl.getDomain(hn) || hn); + } + const entityBeg = hn.length - dn.length; + if ( entityBeg !== 0 ) { + addSlice(entityBeg, style); + } + let entityEnd = dn.indexOf('.'); + if ( entityEnd === -1 ) { entityEnd = dn.length; } + addSlice(entityEnd, style !== null ? `${style} strong` : 'strong'); + if ( entityEnd < dn.length ) { + addSlice(dn.length - entityEnd, style); + } + return match.input.slice(hn.length); + }; + + const makeSlices = (stream, opts) => { + sliceIndex = 0; + sliceCount = 0; + let { string } = stream; + if ( string === '...' ) { return; } + const { sortType } = opts; + const reNotToken = /^\s+/; + const reToken = /^\S+/; + const tokens = []; + // leading whitespaces + let match = reNotToken.exec(string); + if ( match !== null ) { + string = addMatchSlice(match); + } + // first token + match = reToken.exec(string); + if ( match === null ) { return; } + tokens.push(match[0]); + // hostname or switch + const isSwitchRule = validSwitches.has(match[0]); + if ( isSwitchRule ) { + string = addMatchSlice(match, sortType === 0 ? 'sortkey' : null); + } else if ( isValidHostname(match[0]) ) { + if ( sortType === 1 ) { + string = addMatchHnSlices(match, 'sortkey'); + } else { + string = addMatchHnSlices(match, null); + } + } else { + string = addMatchSlice(match, 'error'); + } + // whitespaces before second token + match = reNotToken.exec(string); + if ( match === null ) { return; } + string = addMatchSlice(match); + // second token + match = reToken.exec(string); + if ( match === null ) { return; } + tokens.push(match[0]); + // hostname or url + const isURLRule = isSwitchRule === false && match[0].indexOf('://') > 0; + if ( isURLRule ) { + string = addMatchSlice(match, sortType === 2 ? 'sortkey' : null); + } else if ( isValidHostname(match[0]) === false ) { + string = addMatchSlice(match, 'error'); + } else if ( sortType === 1 && isSwitchRule || sortType === 2 ) { + string = addMatchHnSlices(match, 'sortkey'); + } else { + string = addMatchHnSlices(match, null); + } + // whitespaces before third token + match = reNotToken.exec(string); + if ( match === null ) { return; } + string = addMatchSlice(match); + // third token + match = reToken.exec(string); + if ( match === null ) { return; } + tokens.push(match[0]); + // rule type or switch state + if ( isSwitchRule ) { + string = validSwitcheStates.has(match[0]) + ? addMatchSlice(match, match[0] === 'true' ? 'blockrule' : 'allowrule') + : addMatchSlice(match, 'error'); + } else if ( isURLRule ) { + string = invalidURLRuleTypes.has(match[0]) + ? addMatchSlice(match, 'error') + : addMatchSlice(match); + } else if ( tokens[1] === '*' ) { + string = validHnRuleTypes.has(match[0]) + ? addMatchSlice(match) + : addMatchSlice(match, 'error'); + } else { + string = match[0] === '*' + ? addMatchSlice(match) + : addMatchSlice(match, 'error'); + } + // whitespaces before fourth token + match = reNotToken.exec(string); + if ( match === null ) { return; } + string = addMatchSlice(match); + // fourth token + match = reToken.exec(string); + if ( match === null ) { return; } + tokens.push(match[0]); + string = isSwitchRule || validActions.has(match[0]) === false + ? addMatchSlice(match, 'error') + : addMatchSlice(match, `${match[0]}rule`); + // whitespaces before end of line + match = reNotToken.exec(string); + if ( match === null ) { return; } + string = addMatchSlice(match); + // any token beyond fourth token is invalid + match = reToken.exec(string); + if ( match !== null ) { + string = addMatchSlice(null, 'error'); + } + }; + + const token = function(stream) { + if ( stream.sol() ) { + makeSlices(stream, this); + } + if ( sliceIndex >= sliceCount ) { + stream.skipToEnd(stream); + return null; + } + const { len, style } = slices[sliceIndex++]; + if ( len === 0 ) { + stream.skipToEnd(); + } else { + stream.pos += len; + } + return style; + }; + + return { + token, + sortType: 1, + setHostnameToDomainMap: a => { hostnameToDomainMap = a; }, + setPSL: a => { psl = a; }, + }; +}); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/codemirror/ubo-static-filtering.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/codemirror/ubo-static-filtering.js new file mode 100644 index 0000000..645879a --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/codemirror/ubo-static-filtering.js @@ -0,0 +1,867 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2018-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 +*/ + +/* global CodeMirror */ + +'use strict'; + +/******************************************************************************/ + +import { StaticFilteringParser } from '../static-filtering-parser.js'; + +/******************************************************************************/ + +const redirectNames = new Map(); +const scriptletNames = new Map(); +const preparseDirectiveTokens = new Map(); +const preparseDirectiveHints = []; +const originHints = []; +let hintHelperRegistered = false; + +/******************************************************************************/ + +CodeMirror.defineMode('ubo-static-filtering', function() { + if ( StaticFilteringParser instanceof Object === false ) { return; } + const parser = new StaticFilteringParser({ interactive: true }); + + const reURL = /\bhttps?:\/\/\S+/; + const rePreparseDirectives = /^!#(?:if|endif|include )\b/; + const rePreparseIfDirective = /^(!#if ?)(.*)$/; + let parserSlot = 0; + let netOptionValueMode = false; + + const colorCommentSpan = function(stream) { + const { string, pos } = stream; + if ( rePreparseDirectives.test(string) === false ) { + const match = reURL.exec(string.slice(pos)); + if ( match !== null ) { + if ( match.index === 0 ) { + stream.pos += match[0].length; + return 'comment link'; + } + stream.pos += match.index; + return 'comment'; + } + stream.skipToEnd(); + return 'comment'; + } + const match = rePreparseIfDirective.exec(string); + if ( match === null ) { + stream.skipToEnd(); + return 'directive'; + } + if ( pos < match[1].length ) { + stream.pos += match[1].length; + return 'directive'; + } + stream.skipToEnd(); + if ( match[1].endsWith(' ') === false ) { + return 'error strong'; + } + if ( preparseDirectiveTokens.size === 0 ) { + return 'positive strong'; + } + let token = match[2]; + const not = token.startsWith('!'); + if ( not ) { + token = token.slice(1); + } + if ( preparseDirectiveTokens.has(token) === false ) { + return 'error strong'; + } + if ( not !== preparseDirectiveTokens.get(token) ) { + return 'positive strong'; + } + return 'negative strong'; + }; + + const colorExtHTMLPatternSpan = function(stream) { + const { i } = parser.patternSpan; + if ( stream.pos === parser.slices[i+1] ) { + stream.pos += 1; + return 'def'; + } + stream.skipToEnd(); + return 'variable'; + }; + + const colorExtScriptletPatternSpan = function(stream) { + const { pos, string } = stream; + const { i, len } = parser.patternSpan; + const patternBeg = parser.slices[i+1]; + if ( pos === patternBeg ) { + stream.pos = pos + 4; + return 'def'; + } + if ( len > 3 ) { + if ( pos === patternBeg + 4 ) { + const match = /^[^,)]+/.exec(string.slice(pos)); + const token = match && match[0].trim(); + if ( token && scriptletNames.has(token) === false ) { + stream.pos = pos + match[0].length; + return 'warning'; + } + } + const r = parser.slices[i+len+1] - 1; + if ( pos < r ) { + stream.pos = r; + return 'variable'; + } + if ( pos === r ) { + stream.pos = pos + 1; + return 'def'; + } + } + stream.skipToEnd(); + return 'variable'; + }; + + const colorExtPatternSpan = function(stream) { + if ( (parser.flavorBits & parser.BITFlavorExtScriptlet) !== 0 ) { + return colorExtScriptletPatternSpan(stream); + } + if ( (parser.flavorBits & parser.BITFlavorExtHTML) !== 0 ) { + return colorExtHTMLPatternSpan(stream); + } + stream.skipToEnd(); + return 'variable'; + }; + + const colorExtSpan = function(stream) { + if ( parserSlot < parser.optionsAnchorSpan.i ) { + const style = (parser.slices[parserSlot] & parser.BITComma) === 0 + ? 'value' + : 'def'; + stream.pos += parser.slices[parserSlot+2]; + parserSlot += 3; + return style; + } + if ( + parserSlot >= parser.optionsAnchorSpan.i && + parserSlot < parser.patternSpan.i + ) { + const style = (parser.flavorBits & parser.BITFlavorException) !== 0 + ? 'tag' + : 'def'; + stream.pos += parser.slices[parserSlot+2]; + parserSlot += 3; + return `${style} strong`; + } + if ( + parserSlot >= parser.patternSpan.i && + parserSlot < parser.rightSpaceSpan.i + ) { + return colorExtPatternSpan(stream); + } + stream.skipToEnd(); + return null; + }; + + const colorNetOptionValueSpan = function(stream, bits) { + const { pos, string } = stream; + let style; + // Warn about unknown redirect tokens. + if ( + string.charCodeAt(pos - 1) === 0x3D /* '=' */ && + /[$,](redirect(-rule)?|rewrite)=$/.test(string.slice(0, pos)) + ) { + style = 'value'; + const end = parser.skipUntil( + parserSlot, + parser.commentSpan.i, + parser.BITComma + ); + const raw = parser.strFromSlices(parserSlot, end - 3); + const { token } = StaticFilteringParser.parseRedirectValue(raw); + if ( redirectNames.has(token) === false ) { + style += ' warning'; + } + stream.pos += raw.length; + parserSlot = end; + return style; + } + if ( (bits & parser.BITTilde) !== 0 ) { + style = 'keyword strong'; + } else if ( (bits & parser.BITPipe) !== 0 ) { + style = 'def'; + } + stream.pos += parser.slices[parserSlot+2]; + parserSlot += 3; + return style || 'value'; + }; + + // https://github.com/uBlockOrigin/uBlock-issues/issues/760#issuecomment-951146371 + // Quick fix: auto-escape commas. + const colorNetOptionSpan = function(stream) { + const [ slotBits, slotPos, slotLen ] = + parser.slices.slice(parserSlot, parserSlot+3); + if ( (slotBits & parser.BITComma) !== 0 ) { + if ( /^,\d*?\}/.test(parser.raw.slice(slotPos)) === false ) { + netOptionValueMode = false; + stream.pos += slotLen; + parserSlot += 3; + return 'def strong'; + } + } + if ( netOptionValueMode ) { + return colorNetOptionValueSpan(stream, slotBits); + } + if ( (slotBits & parser.BITTilde) !== 0 ) { + stream.pos += slotLen; + parserSlot += 3; + return 'keyword strong'; + } + if ( (slotBits & parser.BITEqual) !== 0 ) { + netOptionValueMode = true; + stream.pos += slotLen; + parserSlot += 3; + return 'def'; + } + parserSlot = parser.skipUntil( + parserSlot, + parser.commentSpan.i, + parser.BITComma | parser.BITEqual + ); + stream.pos = parser.slices[parserSlot+1]; + return 'def'; + }; + + const colorNetSpan = function(stream) { + if ( parserSlot < parser.exceptionSpan.i ) { + stream.pos += parser.slices[parserSlot+2]; + parserSlot += 3; + return null; + } + if ( + parserSlot === parser.exceptionSpan.i && + parser.exceptionSpan.len !== 0 + ) { + stream.pos += parser.slices[parserSlot+2]; + parserSlot += 3; + return 'tag strong'; + } + if ( + parserSlot === parser.patternLeftAnchorSpan.i && + parser.patternLeftAnchorSpan.len !== 0 || + parserSlot === parser.patternRightAnchorSpan.i && + parser.patternRightAnchorSpan.len !== 0 + ) { + stream.pos += parser.slices[parserSlot+2]; + parserSlot += 3; + return 'keyword strong'; + } + if ( + parserSlot >= parser.patternSpan.i && + parserSlot < parser.optionsAnchorSpan.i + ) { + if ( parser.patternIsRegex() ) { + stream.pos = parser.slices[parser.optionsAnchorSpan.i+1]; + parserSlot = parser.optionsAnchorSpan.i; + return parser.patternIsTokenizable() + ? 'variable notice' + : 'variable warning'; + } + if ( (parser.slices[parserSlot] & (parser.BITAsterisk | parser.BITCaret)) !== 0 ) { + stream.pos += parser.slices[parserSlot+2]; + parserSlot += 3; + return 'keyword strong'; + } + const nextSlot = parser.skipUntil( + parserSlot + 3, + parser.patternRightAnchorSpan.i, + parser.BITAsterisk | parser.BITCaret + ); + stream.pos = parser.slices[nextSlot+1]; + parserSlot = nextSlot; + return 'variable'; + } + if ( + parserSlot === parser.optionsAnchorSpan.i && + parserSlot < parser.optionsSpan.i !== 0 + ) { + stream.pos += parser.slices[parserSlot+2]; + parserSlot += 3; + return 'def strong'; + } + if ( + parserSlot >= parser.optionsSpan.i && + parserSlot < parser.commentSpan.i + ) { + return colorNetOptionSpan(stream); + } + if ( + parserSlot >= parser.commentSpan.i && + parser.commentSpan.len !== 0 + ) { + stream.skipToEnd(); + return 'comment'; + } + stream.skipToEnd(); + return null; + }; + + const colorSpan = function(stream) { + if ( parser.category === parser.CATNone || parser.shouldIgnore() ) { + stream.skipToEnd(); + return 'comment'; + } + if ( parser.category === parser.CATComment ) { + return colorCommentSpan(stream); + } + if ( (parser.slices[parserSlot] & parser.BITError) !== 0 ) { + stream.pos += parser.slices[parserSlot+2]; + parserSlot += 3; + return 'error'; + } + if ( (parser.slices[parserSlot] & parser.BITIgnore) !== 0 ) { + stream.pos += parser.slices[parserSlot+2]; + parserSlot += 3; + return 'comment'; + } + if ( parser.category === parser.CATStaticExtFilter ) { + const style = colorExtSpan(stream) || ''; + let flavor = ''; + if ( (parser.flavorBits & parser.BITFlavorExtCosmetic) !== 0 ) { + flavor = 'line-cm-ext-dom'; + } else if ( (parser.flavorBits & parser.BITFlavorExtScriptlet) !== 0 ) { + flavor = 'line-cm-ext-js'; + } else if ( (parser.flavorBits & parser.BITFlavorExtHTML) !== 0 ) { + flavor = 'line-cm-ext-html'; + } + return `${flavor} ${style}`.trim(); + } + if ( parser.category === parser.CATStaticNetFilter ) { + const style = colorNetSpan(stream); + return style ? `line-cm-net ${style}` : 'line-cm-net'; + } + stream.skipToEnd(); + return null; + }; + + return { + lineComment: '!', + token: function(stream) { + let style = ''; + if ( stream.sol() ) { + parser.analyze(stream.string); + parser.analyzeExtra(); + parserSlot = 0; + netOptionValueMode = false; + } + style += colorSpan(stream) || ''; + if ( (parser.flavorBits & parser.BITFlavorError) !== 0 ) { + style += ' line-background-error'; + } + style = style.trim(); + return style !== '' ? style : null; + }, + setHints: function(details) { + if ( Array.isArray(details.redirectResources) ) { + for ( const [ name, desc ] of details.redirectResources ) { + const displayText = desc.aliasOf !== '' + ? `${name} (${desc.aliasOf})` + : ''; + if ( desc.canRedirect ) { + redirectNames.set(name, displayText); + } + if ( desc.canInject && name.endsWith('.js') ) { + scriptletNames.set(name.slice(0, -3), displayText); + } + } + } + if ( Array.isArray(details.preparseDirectiveTokens)) { + details.preparseDirectiveTokens.forEach(([ a, b ]) => { + preparseDirectiveTokens.set(a, b); + }); + } + if ( Array.isArray(details.preparseDirectiveHints)) { + preparseDirectiveHints.push(...details.preparseDirectiveHints); + } + if ( Array.isArray(details.originHints) ) { + originHints.length = 0; + for ( const hint of details.originHints ) { + originHints.push(hint); + } + } + if ( hintHelperRegistered === false ) { + hintHelperRegistered = true; + initHints(); + } + }, + get parser() { + return parser; + }, + }; +}); + +/******************************************************************************/ + +// Following code is for auto-completion. Reference: +// https://codemirror.net/demo/complete.html + +const initHints = function() { + if ( StaticFilteringParser instanceof Object === false ) { return; } + + const parser = new StaticFilteringParser(); + const proceduralOperatorNames = new Map( + Array.from(parser.proceduralOperatorTokens) + .filter(item => (item[1] & 0b01) !== 0) + ); + const excludedHints = new Set([ + 'genericblock', + 'object-subrequest', + 'rewrite', + 'webrtc', + ]); + + const pickBestHints = function(cursor, seedLeft, seedRight, hints) { + const seed = (seedLeft + seedRight).trim(); + const out = []; + // First, compare against whole seed + for ( const hint of hints ) { + const text = hint instanceof Object + ? hint.displayText || hint.text + : hint; + if ( text.startsWith(seed) === false ) { continue; } + out.push(hint); + } + if ( out.length !== 0 ) { + return { + from: { line: cursor.line, ch: cursor.ch - seedLeft.length }, + to: { line: cursor.line, ch: cursor.ch + seedRight.length }, + list: out, + }; + } + // If no match, try again with a different heuristic: valid hints are + // those matching left seed, not matching right seed but right seed is + // found to be a valid hint. This is to take care of cases like: + // + // *$script,redomain=example.org + // ^ + // + cursor position + // + // In such case, [ redirect=, redirect-rule= ] should be returned + // as valid hints. + for ( const hint of hints ) { + const text = hint instanceof Object + ? hint.displayText || hint.text + : hint; + if ( seedLeft.length === 0 ) { continue; } + if ( text.startsWith(seedLeft) === false ) { continue; } + if ( hints.includes(seedRight) === false ) { continue; } + out.push(hint); + } + if ( out.length !== 0 ) { + return { + from: { line: cursor.line, ch: cursor.ch - seedLeft.length }, + to: { line: cursor.line, ch: cursor.ch }, + list: out, + }; + } + // If no match, try again with a different heuristic: valid hints are + // those containing seed as a substring. This is to take care of cases + // like: + // + // *$script,redirect=gif + // ^ + // + cursor position + // + // In such case, [ 1x1.gif, 1x1-transparent.gif ] should be returned + // as valid hints. + for ( const hint of hints ) { + const text = hint instanceof Object + ? hint.displayText || hint.text + : hint; + if ( seedLeft.length === 1 ) { + if ( text.startsWith(seedLeft) === false ) { continue; } + } else if ( text.includes(seed) === false ) { continue; } + out.push(hint); + } + if ( out.length !== 0 ) { + return { + from: { line: cursor.line, ch: cursor.ch - seedLeft.length }, + to: { line: cursor.line, ch: cursor.ch + seedRight.length }, + list: out, + }; + } + // If still no match, try again with a different heuristic: valid hints + // are those containing left seed as a substring. This is to take care + // of cases like: + // + // *$script,redirect=gifdomain=example.org + // ^ + // + cursor position + // + // In such case, [ 1x1.gif, 1x1-transparent.gif ] should be returned + // as valid hints. + for ( const hint of hints ) { + const text = hint instanceof Object + ? hint.displayText || hint.text + : hint; + if ( text.includes(seedLeft) === false ) { continue; } + out.push(hint); + } + if ( out.length !== 0 ) { + return { + from: { line: cursor.line, ch: cursor.ch - seedLeft.length }, + to: { line: cursor.line, ch: cursor.ch }, + list: out, + }; + } + }; + + const getOriginHints = function(cursor, line, suffix = '') { + const beg = cursor.ch; + const matchLeft = /[^,|=~]*$/.exec(line.slice(0, beg)); + const matchRight = /^[^#,|]*/.exec(line.slice(beg)); + if ( matchLeft === null || matchRight === null ) { return; } + const hints = []; + for ( const text of originHints ) { + hints.push(text + suffix); + } + return pickBestHints(cursor, matchLeft[0], matchRight[0], hints); + }; + + const getNetPatternHints = function(cursor, line) { + if ( /\|\|[\w.-]*$/.test(line.slice(0, cursor.ch)) ) { + return getOriginHints(cursor, line, '^'); + } + // Maybe a static extended filter is meant to be crafted. + if ( /[^\w\x80-\xF4#,.-]/.test(line) === false ) { + return getOriginHints(cursor, line); + } + }; + + const getNetOptionHints = function(cursor, seedLeft, seedRight) { + const isNegated = seedLeft.startsWith('~'); + if ( isNegated ) { + seedLeft = seedLeft.slice(1); + } + const assignPos = seedRight.indexOf('='); + if ( assignPos !== -1 ) { seedRight = seedRight.slice(0, assignPos); } + const isException = parser.isException(); + const hints = []; + for ( let [ text, bits ] of parser.netOptionTokenDescriptors ) { + if ( excludedHints.has(text) ) { continue; } + if ( isNegated && (bits & parser.OPTCanNegate) === 0 ) { continue; } + if ( isException ) { + if ( (bits & parser.OPTBlockOnly) !== 0 ) { continue; } + } else { + if ( (bits & parser.OPTAllowOnly) !== 0 ) { continue; } + if ( (assignPos === -1) && (bits & parser.OPTMustAssign) !== 0 ) { + text += '='; + } + } + hints.push(text); + } + return pickBestHints(cursor, seedLeft, seedRight, hints); + }; + + const getNetRedirectHints = function(cursor, seedLeft, seedRight) { + const hints = []; + for ( const text of redirectNames.keys() ) { + if ( text.startsWith('abp-resource:') ) { continue; } + hints.push(text); + } + return pickBestHints(cursor, seedLeft, seedRight, hints); + }; + + const getNetHints = function(cursor, line) { + const beg = cursor.ch; + if ( beg <= parser.slices[parser.optionsAnchorSpan.i+1] ) { + return getNetPatternHints(cursor, line); + } + const lineBefore = line.slice(0, beg); + const lineAfter = line.slice(beg); + let matchLeft = /[^$,]*$/.exec(lineBefore); + let matchRight = /^[^,]*/.exec(lineAfter); + if ( matchLeft === null || matchRight === null ) { return; } + const assignPos = matchLeft[0].indexOf('='); + if ( assignPos === -1 ) { + return getNetOptionHints(cursor, matchLeft[0], matchRight[0]); + } + if ( /^(redirect(-rule)?|rewrite)=/.test(matchLeft[0]) ) { + return getNetRedirectHints( + cursor, + matchLeft[0].slice(assignPos + 1), + matchRight[0] + ); + } + if ( matchLeft[0].startsWith('domain=') ) { + return getOriginHints(cursor, line); + } + }; + + const getExtSelectorHints = function(cursor, line) { + const beg = cursor.ch; + // Special selector case: `^responseheader` + { + const match = /#\^([a-z]+)$/.exec(line.slice(0, beg)); + if ( + match !== null && + 'responseheader'.startsWith(match[1]) && + line.slice(beg) === '' + ) { + return pickBestHints( + cursor, + match[1], + '', + [ 'responseheader()' ] + ); + } + } + // Procedural operators + const matchLeft = /#\^?.*:([^:]*)$/.exec(line.slice(0, beg)); + const matchRight = /^([a-z-]*)\(?/.exec(line.slice(beg)); + if ( matchLeft === null || matchRight === null ) { return; } + const isStaticDOM = matchLeft[0].indexOf('^') !== -1; + const hints = []; + for ( let [ text, bits ] of proceduralOperatorNames ) { + if ( isStaticDOM && (bits & 0b10) !== 0 ) { continue; } + hints.push(text); + } + return pickBestHints(cursor, matchLeft[1], matchRight[1], hints); + }; + + const getExtHeaderHints = function(cursor, line) { + const beg = cursor.ch; + const matchLeft = /#\^responseheader\((.*)$/.exec(line.slice(0, beg)); + const matchRight = /^([^)]*)/.exec(line.slice(beg)); + if ( matchLeft === null || matchRight === null ) { return; } + const hints = []; + for ( const hint of parser.removableHTTPHeaders ) { + hints.push(hint); + } + return pickBestHints(cursor, matchLeft[1], matchRight[1], hints); + }; + + const getExtScriptletHints = function(cursor, line) { + const beg = cursor.ch; + const matchLeft = /#\+\js\(([^,]*)$/.exec(line.slice(0, beg)); + const matchRight = /^([^,)]*)/.exec(line.slice(beg)); + if ( matchLeft === null || matchRight === null ) { return; } + const hints = []; + for ( const [ text, displayText ] of scriptletNames ) { + const hint = { text }; + if ( displayText !== '' ) { + hint.displayText = displayText; + } + hints.push(hint); + } + return pickBestHints(cursor, matchLeft[1], matchRight[1], hints); + }; + + const getCommentHints = function(cursor, line) { + const beg = cursor.ch; + if ( line.startsWith('!#if ') ) { + const matchLeft = /^!#if !?(\w*)$/.exec(line.slice(0, beg)); + const matchRight = /^\w*/.exec(line.slice(beg)); + if ( matchLeft === null || matchRight === null ) { return; } + return pickBestHints( + cursor, + matchLeft[1], + matchRight[0], + preparseDirectiveHints + ); + } + if ( line.startsWith('!#') && line !== '!#endif' ) { + const matchLeft = /^!#(\w*)$/.exec(line.slice(0, beg)); + const matchRight = /^\w*/.exec(line.slice(beg)); + if ( matchLeft === null || matchRight === null ) { return; } + const hints = [ 'if ', 'endif\n', 'include ' ]; + return pickBestHints(cursor, matchLeft[1], matchRight[0], hints); + } + }; + + CodeMirror.registerHelper('hint', 'ubo-static-filtering', function(cm) { + const cursor = cm.getCursor(); + const line = cm.getLine(cursor.line); + parser.analyze(line); + if ( parser.category === parser.CATStaticExtFilter ) { + let hints; + if ( cursor.ch <= parser.slices[parser.optionsAnchorSpan.i+1] ) { + hints = getOriginHints(cursor, line); + } else if ( parser.hasFlavor(parser.BITFlavorExtScriptlet) ) { + hints = getExtScriptletHints(cursor, line); + } else if ( parser.hasFlavor(parser.BITFlavorExtResponseHeader) ) { + hints = getExtHeaderHints(cursor, line); + } else { + hints = getExtSelectorHints(cursor, line); + } + return hints; + } + if ( parser.category === parser.CATStaticNetFilter ) { + return getNetHints(cursor, line); + } + if ( parser.category === parser.CATComment ) { + return getCommentHints(cursor, line); + } + if ( parser.category === parser.CATNone ) { + return getOriginHints(cursor, line); + } + }); +}; + +/******************************************************************************/ + +CodeMirror.registerHelper('fold', 'ubo-static-filtering', (( ) => { + const foldIfEndif = function(startLineNo, startLine, cm) { + const lastLineNo = cm.lastLine(); + let endLineNo = startLineNo; + let depth = 1; + while ( endLineNo < lastLineNo ) { + endLineNo += 1; + const line = cm.getLine(endLineNo); + if ( line.startsWith('!#endif') ) { + depth -= 1; + if ( depth === 0 ) { + return { + from: CodeMirror.Pos(startLineNo, startLine.length), + to: CodeMirror.Pos(endLineNo, 0) + }; + } + } + if ( line.startsWith('!#if') ) { + depth += 1; + } + } + }; + + const foldInclude = function(startLineNo, startLine, cm) { + const lastLineNo = cm.lastLine(); + let endLineNo = startLineNo + 1; + if ( endLineNo >= lastLineNo ) { return; } + if ( cm.getLine(endLineNo).startsWith('! >>>>>>>> ') === false ) { + return; + } + while ( endLineNo < lastLineNo ) { + endLineNo += 1; + const line = cm.getLine(endLineNo); + if ( line.startsWith('! <<<<<<<< ') ) { + return { + from: CodeMirror.Pos(startLineNo, startLine.length), + to: CodeMirror.Pos(endLineNo, line.length) + }; + } + } + }; + + return function(cm, start) { + const startLineNo = start.line; + const startLine = cm.getLine(startLineNo); + if ( startLine.startsWith('!#if') ) { + return foldIfEndif(startLineNo, startLine, cm); + } + if ( startLine.startsWith('!#include ') ) { + return foldInclude(startLineNo, startLine, cm); + } + }; +})()); + +/******************************************************************************/ + +// Enhanced word selection + +{ + const selectWordAt = function(cm, pos) { + const { line, ch } = pos; + const s = cm.getLine(line); + const { type: token } = cm.getTokenAt(pos); + let beg, end; + + // Select URL in comments + if ( /\bcomment\b/.test(token) && /\blink\b/.test(token) ) { + const l = /\S+$/.exec(s.slice(0, ch)); + if ( l && /^https?:\/\//.test(s.slice(l.index)) ) { + const r = /^\S+/.exec(s.slice(ch)); + if ( r ) { + beg = l.index; + end = ch + r[0].length; + } + } + } + + // Better word selection for extended filters: prefix + else if ( + /\bline-cm-ext-(?:dom|html|js)\b/.test(token) && + /\bvalue\b/.test(token) + ) { + const l = /[^,.]*$/i.exec(s.slice(0, ch)); + const r = /^[^#,]*/i.exec(s.slice(ch)); + if ( l && r ) { + beg = l.index; + end = ch + r[0].length; + } + } + + // Better word selection for cosmetic and HTML filters: suffix + else if ( /\bline-cm-ext-(?:dom|html)\b/.test(token) ) { + const l = /[#.]?[a-z0-9_-]+$/i.exec(s.slice(0, ch)); + const r = /^[a-z0-9_-]+/i.exec(s.slice(ch)); + if ( l && r ) { + beg = l.index; + end = ch + r[0].length; + if ( /\bdef\b/.test(cm.getTokenTypeAt({ line, ch: beg + 1 })) ) { + beg += 1; + } + } + } + + // Better word selection for network filters + else if ( /\bline-cm-net\b/.test(token) ) { + if ( /\bvalue\b/.test(token) ) { + const l = /[^ ,.=|]*$/i.exec(s.slice(0, ch)); + const r = /^[^ #,|]*/i.exec(s.slice(ch)); + if ( l && r ) { + beg = l.index; + end = ch + r[0].length; + } + } else if ( /\bdef\b/.test(token) ) { + const l = /[a-z0-9-]+$/i.exec(s.slice(0, ch)); + const r = /^[^,]*=[^,]+/i.exec(s.slice(ch)); + if ( l && r ) { + beg = l.index; + end = ch + r[0].length; + } + } + } + + if ( beg === undefined ) { + const { anchor, head } = cm.findWordAt(pos); + return { from: anchor, to: head }; + } + + return { + from: { line, ch: beg }, + to: { line, ch: end }, + }; + }; + + CodeMirror.defineInitHook(cm => { + cm.setOption('configureMouse', function(cm, repeat) { + return { + unit: repeat === 'double' ? selectWordAt : null, + }; + }); + }); +} + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/commands.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/commands.js new file mode 100644 index 0000000..1a89865 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/commands.js @@ -0,0 +1,217 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2017-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 +*/ + +'use strict'; + +/******************************************************************************/ + +import µb from './background.js'; +import { hostnameFromURI } from './uri-utils.js'; + +/******************************************************************************/ + +µb.canUseShortcuts = vAPI.commands instanceof Object; + +// https://github.com/uBlockOrigin/uBlock-issues/issues/386 +// Firefox 74 and above has complete shortcut assignment user interface. +µb.canUpdateShortcuts = false; + +if ( + µb.canUseShortcuts && + vAPI.webextFlavor.soup.has('firefox') && + typeof vAPI.commands.update === 'function' +) { + self.addEventListener( + 'webextFlavor', + ( ) => { + µb.canUpdateShortcuts = vAPI.webextFlavor.major < 74; + if ( µb.canUpdateShortcuts === false ) { return; } + vAPI.storage.get('commandShortcuts').then(bin => { + if ( bin instanceof Object === false ) { return; } + const shortcuts = bin.commandShortcuts; + if ( Array.isArray(shortcuts) === false ) { return; } + µb.commandShortcuts = new Map(shortcuts); + for ( const [ name, shortcut ] of shortcuts ) { + vAPI.commands.update({ name, shortcut }); + } + }); + }, + { once: true } + ); +} + +/******************************************************************************/ + +(( ) => { + +// ***************************************************************************** +// start of local namespace + +if ( µb.canUseShortcuts === false ) { return; } + +const relaxBlockingMode = (( ) => { + const reloadTimers = new Map(); + + return function(tab) { + if ( tab instanceof Object === false || tab.id <= 0 ) { return; } + + const normalURL = µb.normalizeTabURL(tab.id, tab.url); + + if ( µb.getNetFilteringSwitch(normalURL) === false ) { return; } + + const hn = hostnameFromURI(normalURL); + const curProfileBits = µb.blockingModeFromHostname(hn); + let newProfileBits; + for ( const profile of µb.liveBlockingProfiles ) { + if ( (curProfileBits & profile.bits & ~1) !== curProfileBits ) { + newProfileBits = profile.bits; + break; + } + } + + // TODO: Reset to original blocking profile? + if ( newProfileBits === undefined ) { return; } + + const noReload = (newProfileBits & 0b00000001) === 0; + + if ( + (curProfileBits & 0b00000010) !== 0 && + (newProfileBits & 0b00000010) === 0 + ) { + µb.toggleHostnameSwitch({ + name: 'no-scripting', + hostname: hn, + state: false, + }); + } + if ( µb.userSettings.advancedUserEnabled ) { + if ( + (curProfileBits & 0b00000100) !== 0 && + (newProfileBits & 0b00000100) === 0 + ) { + µb.toggleFirewallRule({ + tabId: noReload ? tab.id : undefined, + srcHostname: hn, + desHostname: '*', + requestType: '3p', + action: 3, + }); + } + if ( + (curProfileBits & 0b00001000) !== 0 && + (newProfileBits & 0b00001000) === 0 + ) { + µb.toggleFirewallRule({ + srcHostname: hn, + desHostname: '*', + requestType: '3p-script', + action: 3, + }); + } + if ( + (curProfileBits & 0b00010000) !== 0 && + (newProfileBits & 0b00010000) === 0 + ) { + µb.toggleFirewallRule({ + srcHostname: hn, + desHostname: '*', + requestType: '3p-frame', + action: 3, + }); + } + } + + // Reload the target tab? + if ( noReload ) { return; } + + // Reload: use a timer to coalesce bursts of reload commands. + let timer = reloadTimers.get(tab.id); + if ( timer !== undefined ) { + clearTimeout(timer); + } + timer = vAPI.setTimeout( + tabId => { + reloadTimers.delete(tabId); + vAPI.tabs.reload(tabId); + }, + 547, + tab.id + ); + reloadTimers.set(tab.id, timer); + }; +})(); + +vAPI.commands.onCommand.addListener(async command => { + // Generic commands + if ( command === 'open-dashboard' ) { + µb.openNewTab({ + url: 'dashboard.html', + select: true, + index: -1, + }); + return; + } + // Tab-specific commands + const tab = await vAPI.tabs.getCurrent(); + if ( tab instanceof Object === false ) { return; } + switch ( command ) { + case 'launch-element-picker': + case 'launch-element-zapper': { + µb.epickerArgs.mouse = false; + µb.elementPickerExec( + tab.id, + 0, + undefined, + command === 'launch-element-zapper' + ); + break; + } + case 'launch-logger': { + const hash = tab.url.startsWith(vAPI.getURL('')) + ? '' + : `#_+${tab.id}`; + µb.openNewTab({ + url: `logger-ui.html${hash}`, + select: true, + index: -1, + }); + break; + } + case 'relax-blocking-mode': + relaxBlockingMode(tab); + break; + case 'toggle-cosmetic-filtering': + µb.toggleHostnameSwitch({ + name: 'no-cosmetic-filtering', + hostname: hostnameFromURI(µb.normalizeTabURL(tab.id, tab.url)), + }); + break; + default: + break; + } +}); + +// end of local namespace +// ***************************************************************************** + +})(); + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/console.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/console.js new file mode 100644 index 0000000..15e0129 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/console.js @@ -0,0 +1,59 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +'use strict'; + +/******************************************************************************/ + +function ubologSet(state = false) { + if ( state ) { + if ( ubolog.process instanceof Function ) { + ubolog.process(); + } + ubolog = ubologDo; + } else { + ubolog = ubologIgnore; + } +} + +function ubologDo(...args) { + console.info('[uBO]', ...args); +} + +function ubologIgnore() { +} + +let ubolog = (( ) => { + const pending = []; + const store = function(...args) { + pending.push(args); + }; + store.process = function() { + for ( const args of pending ) { + ubologDo(...args); + } + }; + return store; +})(); + +/******************************************************************************/ + +export { ubolog, ubologSet }; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/contentscript-extra.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/contentscript-extra.js new file mode 100644 index 0000000..5c43d72 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/contentscript-extra.js @@ -0,0 +1,569 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +'use strict'; + +if ( + typeof vAPI === 'object' && + typeof vAPI.DOMProceduralFilterer !== 'object' +) { +// >>>>>>>> start of local scope + +/******************************************************************************/ + +const nonVisualElements = { + script: true, + style: true, +}; + +// 'P' stands for 'Procedural' + +class PSelectorTask { + begin() { + } + end() { + } +} + + +class PSelectorHasTextTask extends PSelectorTask { + constructor(task) { + super(); + let arg0 = task[1], arg1; + if ( Array.isArray(task[1]) ) { + arg1 = arg0[1]; arg0 = arg0[0]; + } + this.needle = new RegExp(arg0, arg1); + } + transpose(node, output) { + if ( this.needle.test(node.textContent) ) { + output.push(node); + } + } +} + +class PSelectorIfTask extends PSelectorTask { + constructor(task) { + super(); + this.pselector = new PSelector(task[1]); + } + transpose(node, output) { + if ( this.pselector.test(node) === this.target ) { + output.push(node); + } + } +} +PSelectorIfTask.prototype.target = true; + +class PSelectorIfNotTask extends PSelectorIfTask { +} +PSelectorIfNotTask.prototype.target = false; + +class PSelectorMatchesCSSTask extends PSelectorTask { + constructor(task) { + super(); + this.name = task[1].name; + let arg0 = task[1].value, arg1; + if ( Array.isArray(arg0) ) { + arg1 = arg0[1]; arg0 = arg0[0]; + } + this.value = new RegExp(arg0, arg1); + } + transpose(node, output) { + const style = window.getComputedStyle(node, this.pseudo); + if ( style !== null && this.value.test(style[this.name]) ) { + output.push(node); + } + } +} +PSelectorMatchesCSSTask.prototype.pseudo = null; + +class PSelectorMatchesCSSAfterTask extends PSelectorMatchesCSSTask { +} +PSelectorMatchesCSSAfterTask.prototype.pseudo = ':after'; + +class PSelectorMatchesCSSBeforeTask extends PSelectorMatchesCSSTask { +} +PSelectorMatchesCSSBeforeTask.prototype.pseudo = ':before'; + +class PSelectorMinTextLengthTask extends PSelectorTask { + constructor(task) { + super(); + this.min = task[1]; + } + transpose(node, output) { + if ( node.textContent.length >= this.min ) { + output.push(node); + } + } +} + +class PSelectorMatchesPathTask extends PSelectorTask { + constructor(task) { + super(); + let arg0 = task[1], arg1; + if ( Array.isArray(task[1]) ) { + arg1 = arg0[1]; arg0 = arg0[0]; + } + this.needle = new RegExp(arg0, arg1); + } + transpose(node, output) { + if ( this.needle.test(self.location.pathname + self.location.search) ) { + output.push(node); + } + } +} + +class PSelectorOthersTask extends PSelectorTask { + constructor() { + super(); + this.targets = new Set(); + } + begin() { + this.targets.clear(); + } + end(output) { + const toKeep = new Set(this.targets); + const toDiscard = new Set(); + const body = document.body; + let discard = null; + for ( let keep of this.targets ) { + while ( keep !== null && keep !== body ) { + toKeep.add(keep); + toDiscard.delete(keep); + discard = keep.previousElementSibling; + while ( discard !== null ) { + if ( + nonVisualElements[discard.localName] !== true && + toKeep.has(discard) === false + ) { + toDiscard.add(discard); + } + discard = discard.previousElementSibling; + } + discard = keep.nextElementSibling; + while ( discard !== null ) { + if ( + nonVisualElements[discard.localName] !== true && + toKeep.has(discard) === false + ) { + toDiscard.add(discard); + } + discard = discard.nextElementSibling; + } + keep = keep.parentElement; + } + } + for ( discard of toDiscard ) { + output.push(discard); + } + this.targets.clear(); + } + transpose(candidate) { + for ( const target of this.targets ) { + if ( target.contains(candidate) ) { return; } + if ( candidate.contains(target) ) { + this.targets.delete(target); + } + } + this.targets.add(candidate); + } +} + +// https://github.com/AdguardTeam/ExtendedCss/issues/31#issuecomment-302391277 +// Prepend `:scope ` if needed. +class PSelectorSpathTask extends PSelectorTask { + constructor(task) { + super(); + this.spath = task[1]; + this.nth = /^(?:\s*[+~]|:)/.test(this.spath); + if ( this.nth ) { return; } + if ( /^\s*>/.test(this.spath) ) { + this.spath = `:scope ${this.spath.trim()}`; + } + } + qsa(node) { + if ( this.nth === false ) { + return node.querySelectorAll(this.spath); + } + const parent = node.parentElement; + if ( parent === null ) { return; } + let pos = 1; + for (;;) { + node = node.previousElementSibling; + if ( node === null ) { break; } + pos += 1; + } + return parent.querySelectorAll( + `:scope > :nth-child(${pos})${this.spath}` + ); + } + transpose(node, output) { + const nodes = this.qsa(node); + if ( nodes === undefined ) { return; } + for ( const node of nodes ) { + output.push(node); + } + } +} + +class PSelectorUpwardTask extends PSelectorTask { + constructor(task) { + super(); + const arg = task[1]; + if ( typeof arg === 'number' ) { + this.i = arg; + } else { + this.s = arg; + } + } + transpose(node, output) { + if ( this.s !== '' ) { + const parent = node.parentElement; + if ( parent === null ) { return; } + node = parent.closest(this.s); + if ( node === null ) { return; } + } else { + let nth = this.i; + for (;;) { + node = node.parentElement; + if ( node === null ) { return; } + nth -= 1; + if ( nth === 0 ) { break; } + } + } + output.push(node); + } +} +PSelectorUpwardTask.prototype.i = 0; +PSelectorUpwardTask.prototype.s = ''; + +class PSelectorWatchAttrs extends PSelectorTask { + constructor(task) { + super(); + this.observer = null; + this.observed = new WeakSet(); + this.observerOptions = { + attributes: true, + subtree: true, + }; + const attrs = task[1]; + if ( Array.isArray(attrs) && attrs.length !== 0 ) { + this.observerOptions.attributeFilter = task[1]; + } + } + // TODO: Is it worth trying to re-apply only the current selector? + handler() { + const filterer = + vAPI.domFilterer && vAPI.domFilterer.proceduralFilterer; + if ( filterer instanceof Object ) { + filterer.onDOMChanged([ null ]); + } + } + transpose(node, output) { + output.push(node); + if ( this.observed.has(node) ) { return; } + if ( this.observer === null ) { + this.observer = new MutationObserver(this.handler); + } + this.observer.observe(node, this.observerOptions); + this.observed.add(node); + } +} + +class PSelectorXpathTask extends PSelectorTask { + constructor(task) { + super(); + this.xpe = document.createExpression(task[1], null); + this.xpr = null; + } + transpose(node, output) { + this.xpr = this.xpe.evaluate( + node, + XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, + this.xpr + ); + let j = this.xpr.snapshotLength; + while ( j-- ) { + const node = this.xpr.snapshotItem(j); + if ( node.nodeType === 1 ) { + output.push(node); + } + } + } +} + +class PSelector { + constructor(o) { + if ( PSelector.prototype.operatorToTaskMap === undefined ) { + PSelector.prototype.operatorToTaskMap = new Map([ + [ ':has', PSelectorIfTask ], + [ ':has-text', PSelectorHasTextTask ], + [ ':if', PSelectorIfTask ], + [ ':if-not', PSelectorIfNotTask ], + [ ':matches-css', PSelectorMatchesCSSTask ], + [ ':matches-css-after', PSelectorMatchesCSSAfterTask ], + [ ':matches-css-before', PSelectorMatchesCSSBeforeTask ], + [ ':matches-path', PSelectorMatchesPathTask ], + [ ':min-text-length', PSelectorMinTextLengthTask ], + [ ':not', PSelectorIfNotTask ], + [ ':nth-ancestor', PSelectorUpwardTask ], + [ ':others', PSelectorOthersTask ], + [ ':spath', PSelectorSpathTask ], + [ ':upward', PSelectorUpwardTask ], + [ ':watch-attr', PSelectorWatchAttrs ], + [ ':xpath', PSelectorXpathTask ], + ]); + } + this.raw = o.raw; + this.selector = o.selector; + this.tasks = []; + const tasks = []; + if ( Array.isArray(o.tasks) === false ) { return; } + for ( const task of o.tasks ) { + const ctor = this.operatorToTaskMap.get(task[0]); + if ( ctor === undefined ) { return; } + tasks.push(new ctor(task)); + } + // Initialize only after all tasks have been successfully instantiated + this.tasks = tasks; + } + prime(input) { + const root = input || document; + if ( this.selector === '' ) { return [ root ]; } + return Array.from(root.querySelectorAll(this.selector)); + } + exec(input) { + let nodes = this.prime(input); + for ( const task of this.tasks ) { + if ( nodes.length === 0 ) { break; } + const transposed = []; + task.begin(); + for ( const node of nodes ) { + task.transpose(node, transposed); + } + task.end(transposed); + nodes = transposed; + } + return nodes; + } + test(input) { + const nodes = this.prime(input); + for ( const node of nodes ) { + let output = [ node ]; + for ( const task of this.tasks ) { + const transposed = []; + task.begin(); + for ( const node of output ) { + task.transpose(node, transposed); + } + task.end(transposed); + output = transposed; + if ( output.length === 0 ) { break; } + } + if ( output.length !== 0 ) { return true; } + } + return false; + } +} +PSelector.prototype.operatorToTaskMap = undefined; + +class PSelectorRoot extends PSelector { + constructor(o, styleToken) { + super(o); + this.budget = 200; // I arbitrary picked a 1/5 second + this.raw = o.raw; + this.cost = 0; + this.lastAllowanceTime = 0; + this.styleToken = styleToken; + } +} +PSelectorRoot.prototype.hit = false; + +class ProceduralFilterer { + constructor(domFilterer) { + this.domFilterer = domFilterer; + this.domIsReady = false; + this.domIsWatched = false; + this.mustApplySelectors = false; + this.selectors = new Map(); + this.masterToken = vAPI.randomToken(); + this.styleTokenMap = new Map(); + this.styledNodes = new Set(); + if ( vAPI.domWatcher instanceof Object ) { + vAPI.domWatcher.addListener(this); + } + } + + addProceduralSelectors(selectors) { + const addedSelectors = []; + let mustCommit = this.domIsWatched; + for ( const selector of selectors ) { + if ( this.selectors.has(selector.raw) ) { continue; } + let style, styleToken; + if ( selector.action === undefined ) { + style = vAPI.hideStyle; + } else if ( selector.action[0] === ':style' ) { + style = selector.action[1]; + } + if ( style !== undefined ) { + styleToken = this.styleTokenFromStyle(style); + } + const pselector = new PSelectorRoot(selector, styleToken); + this.selectors.set(selector.raw, pselector); + addedSelectors.push(pselector); + mustCommit = true; + } + if ( mustCommit === false ) { return; } + this.mustApplySelectors = this.selectors.size !== 0; + this.domFilterer.commit(); + if ( this.domFilterer.hasListeners() ) { + this.domFilterer.triggerListeners({ + procedural: addedSelectors + }); + } + } + + commitNow() { + if ( this.selectors.size === 0 || this.domIsReady === false ) { + return; + } + + this.mustApplySelectors = false; + + //console.time('procedural selectors/dom layout changed'); + + // https://github.com/uBlockOrigin/uBlock-issues/issues/341 + // Be ready to unhide nodes which no longer matches any of + // the procedural selectors. + const toUnstyle = this.styledNodes; + this.styledNodes = new Set(); + + let t0 = Date.now(); + + for ( const pselector of this.selectors.values() ) { + const allowance = Math.floor((t0 - pselector.lastAllowanceTime) / 2000); + if ( allowance >= 1 ) { + pselector.budget += allowance * 50; + if ( pselector.budget > 200 ) { pselector.budget = 200; } + pselector.lastAllowanceTime = t0; + } + if ( pselector.budget <= 0 ) { continue; } + const nodes = pselector.exec(); + const t1 = Date.now(); + pselector.budget += t0 - t1; + if ( pselector.budget < -500 ) { + console.info('uBO: disabling %s', pselector.raw); + pselector.budget = -0x7FFFFFFF; + } + t0 = t1; + if ( nodes.length === 0 ) { continue; } + pselector.hit = true; + this.styleNodes(nodes, pselector.styleToken); + } + + this.unstyleNodes(toUnstyle); + //console.timeEnd('procedural selectors/dom layout changed'); + } + + styleTokenFromStyle(style) { + if ( style === undefined ) { return; } + let styleToken = this.styleTokenMap.get(style); + if ( styleToken !== undefined ) { return styleToken; } + styleToken = vAPI.randomToken(); + this.styleTokenMap.set(style, styleToken); + this.domFilterer.addCSS( + `[${this.masterToken}][${styleToken}]\n{${style}}`, + { silent: true, mustInject: true } + ); + return styleToken; + } + + styleNodes(nodes, styleToken) { + if ( styleToken === undefined ) { + for ( const node of nodes ) { + node.textContent = ''; + node.remove(); + } + return; + } + for ( const node of nodes ) { + node.setAttribute(this.masterToken, ''); + node.setAttribute(styleToken, ''); + this.styledNodes.add(node); + } + } + + // TODO: Current assumption is one style per hit element. Could be an + // issue if an element has multiple styling and one styling is + // brough back. Possibly too rare to care about this for now. + unstyleNodes(nodes) { + for ( const node of nodes ) { + if ( this.styledNodes.has(node) ) { continue; } + node.removeAttribute(this.masterToken); + } + } + + createProceduralFilter(o) { + return new PSelectorRoot(o); + } + + onDOMCreated() { + this.domIsReady = true; + this.domFilterer.commit(); + } + + onDOMChanged(addedNodes, removedNodes) { + if ( this.selectors.size === 0 ) { return; } + this.mustApplySelectors = + this.mustApplySelectors || + addedNodes.length !== 0 || + removedNodes; + this.domFilterer.commit(); + } +} + +vAPI.DOMProceduralFilterer = ProceduralFilterer; + +/******************************************************************************/ + +// >>>>>>>> end of local scope +} + + + + + + + + +/******************************************************************************* + + DO NOT: + - Remove the following code + - Add code beyond the following code + Reason: + - https://github.com/gorhill/uBlock/pull/3721 + - uBO never uses the return value from injected content scripts + +**/ + +void 0; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/contentscript.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/contentscript.js new file mode 100644 index 0000000..c2d5661 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/contentscript.js @@ -0,0 +1,1373 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +'use strict'; + +/******************************************************************************* + + +--> domCollapser + | + | + domWatcher--+ + | +-- domSurveyor + | | + +--> domFilterer --+-- [domLogger] + | | + | +-- [domInspector] + | + [domProceduralFilterer] + + domWatcher: + Watches for changes in the DOM, and notify the other components about these + changes. + + domCollapser: + Enforces the collapsing of DOM elements for which a corresponding + resource was blocked through network filtering. + + domFilterer: + Enforces the filtering of DOM elements, by feeding it cosmetic filters. + + domProceduralFilterer: + Enforce the filtering of DOM elements through procedural cosmetic filters. + Loaded on demand, only when needed. + + domSurveyor: + Surveys the DOM to find new cosmetic filters to apply to the current page. + + domLogger: + Surveys the page to find and report the injected cosmetic filters blocking + actual elements on the current page. This component is dynamically loaded + IF AND ONLY IF uBO's logger is opened. + + If page is whitelisted: + - domWatcher: off + - domCollapser: off + - domFilterer: off + - domSurveyor: off + - domLogger: off + + I verified that the code in this file is completely flushed out of memory + when a page is whitelisted. + + If cosmetic filtering is disabled: + - domWatcher: on + - domCollapser: on + - domFilterer: off + - domSurveyor: off + - domLogger: off + + If generic cosmetic filtering is disabled: + - domWatcher: on + - domCollapser: on + - domFilterer: on + - domSurveyor: off + - domLogger: on if uBO logger is opened + + If generic cosmetic filtering is enabled: + - domWatcher: on + - domCollapser: on + - domFilterer: on + - domSurveyor: on + - domLogger: on if uBO logger is opened + + Additionally, the domSurveyor can turn itself off once it decides that + it has become pointless (repeatedly not finding new cosmetic filters). + + The domFilterer makes use of platform-dependent user stylesheets[1]. + + [1] "user stylesheets" refer to local CSS rules which have priority over, + and can't be overridden by a web page's own CSS rules. + +*/ + +// Abort execution if our global vAPI object does not exist. +// https://github.com/chrisaljoudi/uBlock/issues/456 +// https://github.com/gorhill/uBlock/issues/2029 + + // >>>>>>>> start of HUGE-IF-BLOCK +if ( typeof vAPI === 'object' && !vAPI.contentScript ) { + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +vAPI.contentScript = true; + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +// https://github.com/uBlockOrigin/uBlock-issues/issues/688#issuecomment-663657508 +{ + let context = self; + try { + while ( + context !== self.top && + context.location.href.startsWith('about:blank') && + context.parent.location.href + ) { + context = context.parent; + } + } catch(ex) { + } + vAPI.effectiveSelf = context; +} + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +vAPI.userStylesheet = { + added: new Set(), + removed: new Set(), + apply: function(callback) { + if ( this.added.size === 0 && this.removed.size === 0 ) { return; } + vAPI.messaging.send('vapi', { + what: 'userCSS', + add: Array.from(this.added), + remove: Array.from(this.removed), + }).then(( ) => { + if ( callback instanceof Function === false ) { return; } + callback(); + }); + this.added.clear(); + this.removed.clear(); + }, + add: function(cssText, now) { + if ( cssText === '' ) { return; } + this.added.add(cssText); + if ( now ) { this.apply(); } + }, + remove: function(cssText, now) { + if ( cssText === '' ) { return; } + this.removed.add(cssText); + if ( now ) { this.apply(); } + } +}; + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************* + + The purpose of SafeAnimationFrame is to take advantage of the behavior of + window.requestAnimationFrame[1]. If we use an animation frame as a timer, + then this timer is described as follow: + + - time events are throttled by the browser when the viewport is not visible -- + there is no point for uBO to play with the DOM if the document is not + visible. + - time events are micro tasks[2]. + - time events are synchronized to monitor refresh, meaning that they can fire + at most 1/60 (typically). + + If a delay value is provided, a plain timer is first used. Plain timers are + macro-tasks, so this is good when uBO wants to yield to more important tasks + on a page. Once the plain timer elapse, an animation frame is used to trigger + the next time at which to execute the job. + + [1] https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame + [2] https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ + +*/ + +// https://github.com/gorhill/uBlock/issues/2147 + +vAPI.SafeAnimationFrame = class { + constructor(callback) { + this.fid = this.tid = undefined; + this.callback = callback; + } + start(delay) { + if ( self.vAPI instanceof Object === false ) { return; } + if ( delay === undefined ) { + if ( this.fid === undefined ) { + this.fid = requestAnimationFrame(( ) => { this.onRAF(); } ); + } + if ( this.tid === undefined ) { + this.tid = vAPI.setTimeout(( ) => { this.onSTO(); }, 20000); + } + return; + } + if ( this.fid === undefined && this.tid === undefined ) { + this.tid = vAPI.setTimeout(( ) => { this.macroToMicro(); }, delay); + } + } + clear() { + if ( this.fid !== undefined ) { + cancelAnimationFrame(this.fid); + this.fid = undefined; + } + if ( this.tid !== undefined ) { + clearTimeout(this.tid); + this.tid = undefined; + } + } + macroToMicro() { + this.tid = undefined; + this.start(); + } + onRAF() { + if ( this.tid !== undefined ) { + clearTimeout(this.tid); + this.tid = undefined; + } + this.fid = undefined; + this.callback(); + } + onSTO() { + if ( this.fid !== undefined ) { + cancelAnimationFrame(this.fid); + this.fid = undefined; + } + this.tid = undefined; + this.callback(); + } +}; + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +// https://github.com/uBlockOrigin/uBlock-issues/issues/552 +// Listen and report CSP violations so that blocked resources through CSP +// are properly reported in the logger. + +{ + const newEvents = new Set(); + const allEvents = new Set(); + let timer; + + const send = function() { + vAPI.messaging.send('scriptlets', { + what: 'securityPolicyViolation', + type: 'net', + docURL: document.location.href, + violations: Array.from(newEvents), + }).then(response => { + if ( response === true ) { return; } + stop(); + }); + for ( const event of newEvents ) { + allEvents.add(event); + } + newEvents.clear(); + }; + + const sendAsync = function() { + if ( timer !== undefined ) { return; } + timer = self.requestIdleCallback( + ( ) => { timer = undefined; send(); }, + { timeout: 2063 } + ); + }; + + const listener = function(ev) { + if ( ev.isTrusted !== true ) { return; } + if ( ev.disposition !== 'enforce' ) { return; } + const json = JSON.stringify({ + url: ev.blockedURL || ev.blockedURI, + policy: ev.originalPolicy, + directive: ev.effectiveDirective || ev.violatedDirective, + }); + if ( allEvents.has(json) ) { return; } + newEvents.add(json); + sendAsync(); + }; + + const stop = function() { + newEvents.clear(); + allEvents.clear(); + if ( timer !== undefined ) { + self.cancelIdleCallback(timer); + timer = undefined; + } + document.removeEventListener('securitypolicyviolation', listener); + vAPI.shutdown.remove(stop); + }; + + document.addEventListener('securitypolicyviolation', listener); + vAPI.shutdown.add(stop); + + // We need to call at least once to find out whether we really need to + // listen to CSP violations. + sendAsync(); +} + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +// vAPI.domWatcher + +{ + vAPI.domMutationTime = Date.now(); + + const addedNodeLists = []; + const removedNodeLists = []; + const addedNodes = []; + const ignoreTags = new Set([ 'br', 'head', 'link', 'meta', 'script', 'style' ]); + const listeners = []; + + let domLayoutObserver; + let listenerIterator = []; + let listenerIteratorDirty = false; + let removedNodes = false; + let safeObserverHandlerTimer; + + const safeObserverHandler = function() { + let i = addedNodeLists.length; + while ( i-- ) { + const nodeList = addedNodeLists[i]; + let iNode = nodeList.length; + while ( iNode-- ) { + const node = nodeList[iNode]; + if ( node.nodeType !== 1 ) { continue; } + if ( ignoreTags.has(node.localName) ) { continue; } + if ( node.parentElement === null ) { continue; } + addedNodes.push(node); + } + } + addedNodeLists.length = 0; + i = removedNodeLists.length; + while ( i-- && removedNodes === false ) { + const nodeList = removedNodeLists[i]; + let iNode = nodeList.length; + while ( iNode-- ) { + if ( nodeList[iNode].nodeType !== 1 ) { continue; } + removedNodes = true; + break; + } + } + removedNodeLists.length = 0; + if ( addedNodes.length === 0 && removedNodes === false ) { return; } + for ( const listener of getListenerIterator() ) { + try { listener.onDOMChanged(addedNodes, removedNodes); } + catch (ex) { } + } + addedNodes.length = 0; + removedNodes = false; + vAPI.domMutationTime = Date.now(); + }; + + // https://github.com/chrisaljoudi/uBlock/issues/205 + // Do not handle added node directly from within mutation observer. + const observerHandler = function(mutations) { + let i = mutations.length; + while ( i-- ) { + const mutation = mutations[i]; + let nodeList = mutation.addedNodes; + if ( nodeList.length !== 0 ) { + addedNodeLists.push(nodeList); + } + nodeList = mutation.removedNodes; + if ( nodeList.length !== 0 ) { + removedNodeLists.push(nodeList); + } + } + if ( addedNodeLists.length !== 0 || removedNodeLists.length !== 0 ) { + safeObserverHandlerTimer.start( + addedNodeLists.length < 100 ? 1 : undefined + ); + } + }; + + const startMutationObserver = function() { + if ( domLayoutObserver !== undefined ) { return; } + domLayoutObserver = new MutationObserver(observerHandler); + domLayoutObserver.observe(document, { + //attributeFilter: [ 'class', 'id' ], + //attributes: true, + childList: true, + subtree: true + }); + safeObserverHandlerTimer = new vAPI.SafeAnimationFrame(safeObserverHandler); + vAPI.shutdown.add(cleanup); + }; + + const stopMutationObserver = function() { + if ( domLayoutObserver === undefined ) { return; } + cleanup(); + vAPI.shutdown.remove(cleanup); + }; + + const getListenerIterator = function() { + if ( listenerIteratorDirty ) { + listenerIterator = listeners.slice(); + listenerIteratorDirty = false; + } + return listenerIterator; + }; + + const addListener = function(listener) { + if ( listeners.indexOf(listener) !== -1 ) { return; } + listeners.push(listener); + listenerIteratorDirty = true; + if ( domLayoutObserver === undefined ) { return; } + try { listener.onDOMCreated(); } + catch (ex) { } + startMutationObserver(); + }; + + const removeListener = function(listener) { + const pos = listeners.indexOf(listener); + if ( pos === -1 ) { return; } + listeners.splice(pos, 1); + listenerIteratorDirty = true; + if ( listeners.length === 0 ) { + stopMutationObserver(); + } + }; + + const cleanup = function() { + if ( domLayoutObserver !== undefined ) { + domLayoutObserver.disconnect(); + domLayoutObserver = undefined; + } + if ( safeObserverHandlerTimer !== undefined ) { + safeObserverHandlerTimer.clear(); + safeObserverHandlerTimer = undefined; + } + }; + + const start = function() { + for ( const listener of getListenerIterator() ) { + try { listener.onDOMCreated(); } + catch (ex) { } + } + startMutationObserver(); + }; + + vAPI.domWatcher = { start, addListener, removeListener }; +} + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +vAPI.injectScriptlet = function(doc, text) { + if ( !doc ) { return; } + let script; + try { + script = doc.createElement('script'); + script.appendChild(doc.createTextNode(text)); + (doc.head || doc.documentElement || doc).appendChild(script); + } catch (ex) { + } + if ( script ) { + script.remove(); + script.textContent = ''; + } +}; + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************* + + The DOM filterer is the heart of uBO's cosmetic filtering. + + DOMFilterer: adds procedural cosmetic filtering + +*/ + +vAPI.hideStyle = 'display:none!important;'; + +vAPI.DOMFilterer = class { + constructor() { + this.commitTimer = new vAPI.SafeAnimationFrame( + ( ) => { this.commitNow(); } + ); + this.domIsReady = document.readyState !== 'loading'; + this.disabled = false; + this.listeners = []; + this.stylesheets = []; + this.exceptedCSSRules = []; + this.exceptions = []; + this.proceduralFilterer = null; + // https://github.com/uBlockOrigin/uBlock-issues/issues/167 + // By the time the DOMContentLoaded is fired, the content script might + // have been disconnected from the background page. Unclear why this + // would happen, so far seems to be a Chromium-specific behavior at + // launch time. + if ( this.domIsReady !== true ) { + document.addEventListener('DOMContentLoaded', ( ) => { + if ( vAPI instanceof Object === false ) { return; } + this.domIsReady = true; + this.commit(); + }); + } + } + + explodeCSS(css) { + const out = []; + const reBlock = /^\{(.*)\}$/m; + const blocks = css.trim().split(/\n\n+/); + for ( const block of blocks ) { + const match = reBlock.exec(block); + out.push([ block.slice(0, match.index).trim(), match[1] ]); + } + return out; + } + + addCSS(css, details = {}) { + if ( typeof css !== 'string' || css.length === 0 ) { return; } + if ( this.stylesheets.includes(css) ) { return; } + this.stylesheets.push(css); + if ( details.mustInject && this.disabled === false ) { + vAPI.userStylesheet.add(css); + } + if ( this.hasListeners() === false ) { return; } + if ( details.silent ) { return; } + this.triggerListeners({ declarative: this.explodeCSS(css) }); + } + + exceptCSSRules(exceptions) { + if ( exceptions.length === 0 ) { return; } + this.exceptedCSSRules.push(...exceptions); + if ( this.hasListeners() ) { + this.triggerListeners({ exceptions }); + } + } + + addListener(listener) { + if ( this.listeners.indexOf(listener) !== -1 ) { return; } + this.listeners.push(listener); + } + + removeListener(listener) { + const pos = this.listeners.indexOf(listener); + if ( pos === -1 ) { return; } + this.listeners.splice(pos, 1); + } + + hasListeners() { + return this.listeners.length !== 0; + } + + triggerListeners(changes) { + for ( const listener of this.listeners ) { + listener.onFiltersetChanged(changes); + } + } + + toggle(state, callback) { + if ( state === undefined ) { state = this.disabled; } + if ( state !== this.disabled ) { return; } + this.disabled = !state; + const uss = vAPI.userStylesheet; + for ( const css of this.stylesheets ) { + if ( this.disabled ) { + uss.remove(css); + } else { + uss.add(css); + } + } + uss.apply(callback); + } + + // Here we will deal with: + // - Injecting low priority user styles; + // - Notifying listeners about changed filterset. + // https://www.reddit.com/r/uBlockOrigin/comments/9jj0y1/no_longer_blocking_ads/ + // Ensure vAPI is still valid -- it can go away by the time we are + // called, since the port could be force-disconnected from the main + // process. Another approach would be to have vAPI.SafeAnimationFrame + // register a shutdown job: to evaluate. For now I will keep the fix + // trivial. + commitNow() { + this.commitTimer.clear(); + if ( vAPI instanceof Object === false ) { return; } + vAPI.userStylesheet.apply(); + if ( this.proceduralFilterer instanceof Object ) { + this.proceduralFilterer.commitNow(); + } + } + + commit(commitNow) { + if ( commitNow ) { + this.commitTimer.clear(); + this.commitNow(); + } else { + this.commitTimer.start(); + } + } + + proceduralFiltererInstance() { + if ( this.proceduralFilterer instanceof Object === false ) { + if ( vAPI.DOMProceduralFilterer instanceof Object === false ) { + return null; + } + this.proceduralFilterer = new vAPI.DOMProceduralFilterer(this); + } + return this.proceduralFilterer; + } + + addProceduralSelectors(selectors) { + if ( Array.isArray(selectors) === false || selectors.length === 0 ) { + return; + } + const procedurals = []; + for ( const raw of selectors ) { + procedurals.push(JSON.parse(raw)); + } + if ( procedurals.length === 0 ) { return; } + const pfilterer = this.proceduralFiltererInstance(); + if ( pfilterer !== null ) { + pfilterer.addProceduralSelectors(procedurals); + } + } + + createProceduralFilter(o) { + const pfilterer = this.proceduralFiltererInstance(); + if ( pfilterer === null ) { return; } + return pfilterer.createProceduralFilter(o); + } + + getAllSelectors(bits = 0) { + const out = { + declarative: [], + exceptions: this.exceptedCSSRules, + }; + const hasProcedural = this.proceduralFilterer instanceof Object; + const includePrivateSelectors = (bits & 0b01) !== 0; + const masterToken = hasProcedural + ? `[${this.proceduralFilterer.masterToken}]` + : undefined; + for ( const css of this.stylesheets ) { + const blocks = this.explodeCSS(css); + for ( const block of blocks ) { + if ( + includePrivateSelectors === false && + masterToken !== undefined && + block[0].startsWith(masterToken) + ) { + continue; + } + out.declarative.push([ block[0], block[1] ]); + } + } + const excludeProcedurals = (bits & 0b10) !== 0; + if ( excludeProcedurals !== true ) { + out.procedural = hasProcedural + ? Array.from(this.proceduralFilterer.selectors.values()) + : []; + } + return out; + } + + getAllExceptionSelectors() { + return this.exceptions.join(',\n'); + } +}; + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +// vAPI.domCollapser + +{ + const messaging = vAPI.messaging; + const toCollapse = new Map(); + const src1stProps = { + audio: 'currentSrc', + embed: 'src', + iframe: 'src', + img: 'currentSrc', + object: 'data', + video: 'currentSrc', + }; + const src2ndProps = { + audio: 'src', + img: 'src', + video: 'src', + }; + const tagToTypeMap = { + audio: 'media', + embed: 'object', + iframe: 'sub_frame', + img: 'image', + object: 'object', + video: 'media', + }; + let resquestIdGenerator = 1, + processTimer, + cachedBlockedSet, + cachedBlockedSetHash, + cachedBlockedSetTimer, + toProcess = [], + toFilter = [], + netSelectorCacheCount = 0; + + const cachedBlockedSetClear = function() { + cachedBlockedSet = + cachedBlockedSetHash = + cachedBlockedSetTimer = undefined; + }; + + // https://github.com/chrisaljoudi/uBlock/issues/399 + // https://github.com/gorhill/uBlock/issues/2848 + // Use a user stylesheet to collapse placeholders. + const getCollapseToken = ( ) => { + if ( collapseToken === undefined ) { + collapseToken = vAPI.randomToken(); + vAPI.userStylesheet.add( + `[${collapseToken}]\n{display:none!important;}`, + true + ); + } + return collapseToken; + }; + let collapseToken; + + // https://github.com/chrisaljoudi/uBlock/issues/174 + // Do not remove fragment from src URL + const onProcessed = function(response) { + // This happens if uBO is disabled or restarted. + if ( response instanceof Object === false ) { + toCollapse.clear(); + return; + } + + const targets = toCollapse.get(response.id); + if ( targets === undefined ) { return; } + + toCollapse.delete(response.id); + if ( cachedBlockedSetHash !== response.hash ) { + cachedBlockedSet = new Set(response.blockedResources); + cachedBlockedSetHash = response.hash; + if ( cachedBlockedSetTimer !== undefined ) { + clearTimeout(cachedBlockedSetTimer); + } + cachedBlockedSetTimer = vAPI.setTimeout(cachedBlockedSetClear, 30000); + } + if ( cachedBlockedSet === undefined || cachedBlockedSet.size === 0 ) { + return; + } + + const selectors = []; + let netSelectorCacheCountMax = response.netSelectorCacheCountMax; + + for ( const target of targets ) { + const tag = target.localName; + let prop = src1stProps[tag]; + if ( prop === undefined ) { continue; } + let src = target[prop]; + if ( typeof src !== 'string' || src.length === 0 ) { + prop = src2ndProps[tag]; + if ( prop === undefined ) { continue; } + src = target[prop]; + if ( typeof src !== 'string' || src.length === 0 ) { continue; } + } + if ( cachedBlockedSet.has(tagToTypeMap[tag] + ' ' + src) === false ) { + continue; + } + target.setAttribute(getCollapseToken(), ''); + // https://github.com/chrisaljoudi/uBlock/issues/1048 + // Use attribute to construct CSS rule + if ( netSelectorCacheCount > netSelectorCacheCountMax ) { continue; } + const value = target.getAttribute(prop); + if ( value ) { + selectors.push(`${tag}[${prop}="${CSS.escape(value)}"]`); + netSelectorCacheCount += 1; + } + } + + if ( selectors.length === 0 ) { return; } + messaging.send('contentscript', { + what: 'cosmeticFiltersInjected', + type: 'net', + hostname: window.location.hostname, + selectors, + }); + }; + + const send = function() { + processTimer = undefined; + toCollapse.set(resquestIdGenerator, toProcess); + messaging.send('contentscript', { + what: 'getCollapsibleBlockedRequests', + id: resquestIdGenerator, + frameURL: window.location.href, + resources: toFilter, + hash: cachedBlockedSetHash, + }).then(response => { + onProcessed(response); + }); + toProcess = []; + toFilter = []; + resquestIdGenerator += 1; + }; + + const process = function(delay) { + if ( toProcess.length === 0 ) { return; } + if ( delay === 0 ) { + if ( processTimer !== undefined ) { + clearTimeout(processTimer); + } + send(); + } else if ( processTimer === undefined ) { + processTimer = vAPI.setTimeout(send, delay || 20); + } + }; + + const add = function(target) { + toProcess[toProcess.length] = target; + }; + + const addMany = function(targets) { + for ( const target of targets ) { + add(target); + } + }; + + const iframeSourceModified = function(mutations) { + for ( const mutation of mutations ) { + addIFrame(mutation.target, true); + } + process(); + }; + const iframeSourceObserver = new MutationObserver(iframeSourceModified); + const iframeSourceObserverOptions = { + attributes: true, + attributeFilter: [ 'src' ] + }; + + // https://github.com/gorhill/uBlock/issues/162 + // Be prepared to deal with possible change of src attribute. + const addIFrame = function(iframe, dontObserve) { + if ( dontObserve !== true ) { + iframeSourceObserver.observe(iframe, iframeSourceObserverOptions); + } + const src = iframe.src; + if ( typeof src !== 'string' || src === '' ) { return; } + if ( src.startsWith('http') === false ) { return; } + toFilter.push({ type: 'sub_frame', url: iframe.src }); + add(iframe); + }; + + const addIFrames = function(iframes) { + for ( const iframe of iframes ) { + addIFrame(iframe); + } + }; + + const onResourceFailed = function(ev) { + if ( tagToTypeMap[ev.target.localName] !== undefined ) { + add(ev.target); + process(); + } + }; + + const stop = function() { + document.removeEventListener('error', onResourceFailed, true); + if ( processTimer !== undefined ) { + clearTimeout(processTimer); + } + if ( vAPI.domWatcher instanceof Object ) { + vAPI.domWatcher.removeListener(domWatcherInterface); + } + vAPI.shutdown.remove(stop); + vAPI.domCollapser = null; + }; + + const start = function() { + if ( vAPI.domWatcher instanceof Object ) { + vAPI.domWatcher.addListener(domWatcherInterface); + } + }; + + const domWatcherInterface = { + onDOMCreated: function() { + if ( self.vAPI instanceof Object === false ) { return; } + if ( vAPI.domCollapser instanceof Object === false ) { + if ( vAPI.domWatcher instanceof Object ) { + vAPI.domWatcher.removeListener(domWatcherInterface); + } + return; + } + // Listener to collapse blocked resources. + // - Future requests not blocked yet + // - Elements dynamically added to the page + // - Elements which resource URL changes + // https://github.com/chrisaljoudi/uBlock/issues/7 + // Preferring getElementsByTagName over querySelectorAll: + // http://jsperf.com/queryselectorall-vs-getelementsbytagname/145 + const elems = document.images || + document.getElementsByTagName('img'); + for ( const elem of elems ) { + if ( elem.complete ) { + add(elem); + } + } + addMany(document.embeds || document.getElementsByTagName('embed')); + addMany(document.getElementsByTagName('object')); + addIFrames(document.getElementsByTagName('iframe')); + process(0); + + document.addEventListener('error', onResourceFailed, true); + + vAPI.shutdown.add(stop); + }, + onDOMChanged: function(addedNodes) { + if ( addedNodes.length === 0 ) { return; } + for ( const node of addedNodes ) { + if ( node.localName === 'iframe' ) { + addIFrame(node); + } + if ( node.firstElementChild === null ) { continue; } + const iframes = node.getElementsByTagName('iframe'); + if ( iframes.length !== 0 ) { + addIFrames(iframes); + } + } + process(); + } + }; + + vAPI.domCollapser = { start }; +} + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +// vAPI.domSurveyor + +{ + const messaging = vAPI.messaging; + const queriedIds = new Set(); + const queriedClasses = new Set(); + const maxSurveyNodes = 65536; + const maxSurveyTimeSlice = 4; + const maxSurveyBuffer = 64; + + let domFilterer, + hostname = '', + surveyCost = 0; + + const pendingNodes = { + nodeLists: [], + buffer: [ + null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, + ], + j: 0, + accepted: 0, + iterated: 0, + stopped: false, + add(nodes) { + if ( nodes.length === 0 || this.accepted >= maxSurveyNodes ) { + return; + } + this.nodeLists.push(nodes); + this.accepted += nodes.length; + }, + next() { + if ( this.nodeLists.length === 0 || this.stopped ) { return 0; } + const nodeLists = this.nodeLists; + let ib = 0; + do { + const nodeList = nodeLists[0]; + let j = this.j; + let n = j + maxSurveyBuffer - ib; + if ( n > nodeList.length ) { + n = nodeList.length; + } + for ( let i = j; i < n; i++ ) { + this.buffer[ib++] = nodeList[j++]; + } + if ( j !== nodeList.length ) { + this.j = j; + break; + } + this.j = 0; + this.nodeLists.shift(); + } while ( ib < maxSurveyBuffer && nodeLists.length !== 0 ); + this.iterated += ib; + if ( this.iterated >= maxSurveyNodes ) { + this.nodeLists = []; + this.stopped = true; + //console.info(`domSurveyor> Surveyed a total of ${this.iterated} nodes. Enough.`); + } + return ib; + }, + hasNodes() { + return this.nodeLists.length !== 0; + }, + }; + + // Extract all classes/ids: these will be passed to the cosmetic + // filtering engine, and in return we will obtain only the relevant + // CSS selectors. + + // https://github.com/gorhill/uBlock/issues/672 + // http://www.w3.org/TR/2014/REC-html5-20141028/infrastructure.html#space-separated-tokens + // http://jsperf.com/enumerate-classes/6 + + const idFromNode = (node, out) => { + const raw = node.id; + if ( typeof raw !== 'string' || raw.length === 0 ) { return; } + const s = raw.trim(); + if ( queriedIds.has(s) || s.length === 0 ) { return; } + out.push(s); + queriedIds.add(s); + }; + + // https://github.com/uBlockOrigin/uBlock-issues/discussions/2076 + // Performance: avoid using Element.classList + const classesFromNode = (node, out) => { + const s = node.getAttribute('class'); + if ( typeof s !== 'string' ) { return; } + const len = s.length; + for ( let beg = 0, end = 0, token = ''; beg < len; beg += 1 ) { + end = s.indexOf(' ', beg); + if ( end === beg ) { continue; } + if ( end === -1 ) { end = len; } + token = s.slice(beg, end); + beg = end; + if ( queriedClasses.has(token) ) { continue; } + out.push(token); + queriedClasses.add(token); + } + }; + + const surveyPhase1 = function() { + //console.time('dom surveyor/surveying'); + const t0 = performance.now(); + const ids = []; + const classes = []; + const nodes = pendingNodes.buffer; + const deadline = t0 + maxSurveyTimeSlice; + let processed = 0; + for (;;) { + const n = pendingNodes.next(); + if ( n === 0 ) { break; } + for ( let i = 0; i < n; i++ ) { + const node = nodes[i]; nodes[i] = null; + idFromNode(node, ids); + classesFromNode(node, classes); + } + processed += n; + if ( performance.now() >= deadline ) { break; } + } + const t1 = performance.now(); + surveyCost += t1 - t0; + //console.info(`domSurveyor> Surveyed ${processed} nodes in ${(t1-t0).toFixed(2)} ms`); + // Phase 2: Ask main process to lookup relevant cosmetic filters. + if ( ids.length !== 0 || classes.length !== 0 ) { + messaging.send('contentscript', { + what: 'retrieveGenericCosmeticSelectors', + hostname, + ids, classes, + exceptions: domFilterer.exceptions, + cost: surveyCost, + }).then(response => { + surveyPhase3(response); + }); + } else { + surveyPhase3(null); + } + //console.timeEnd('dom surveyor/surveying'); + }; + + const surveyTimer = new vAPI.SafeAnimationFrame(surveyPhase1); + + // This is to shutdown the surveyor if result of surveying keeps being + // fruitless. This is useful on long-lived web page. I arbitrarily + // picked 5 minutes before the surveyor is allowed to shutdown. I also + // arbitrarily picked 256 misses before the surveyor is allowed to + // shutdown. + let canShutdownAfter = Date.now() + 300000, + surveyingMissCount = 0; + + // Handle main process' response. + + const surveyPhase3 = function(response) { + const result = response && response.result; + let mustCommit = false; + + if ( result ) { + const css = result.injectedCSS; + if ( typeof css === 'string' && css.length !== 0 ) { + domFilterer.addCSS(css); + mustCommit = true; + } + const selectors = result.excepted; + if ( Array.isArray(selectors) && selectors.length !== 0 ) { + domFilterer.exceptCSSRules(selectors); + } + } + + if ( pendingNodes.stopped === false ) { + if ( pendingNodes.hasNodes() ) { + surveyTimer.start(1); + } + if ( mustCommit ) { + surveyingMissCount = 0; + canShutdownAfter = Date.now() + 300000; + return; + } + surveyingMissCount += 1; + if ( surveyingMissCount < 256 || Date.now() < canShutdownAfter ) { + return; + } + } + + //console.info('dom surveyor shutting down: too many misses'); + + surveyTimer.clear(); + vAPI.domWatcher.removeListener(domWatcherInterface); + vAPI.domSurveyor = null; + }; + + const domWatcherInterface = { + onDOMCreated: function() { + if ( + self.vAPI instanceof Object === false || + vAPI.domSurveyor instanceof Object === false || + vAPI.domFilterer instanceof Object === false + ) { + if ( self.vAPI instanceof Object ) { + if ( vAPI.domWatcher instanceof Object ) { + vAPI.domWatcher.removeListener(domWatcherInterface); + } + vAPI.domSurveyor = null; + } + return; + } + //console.time('dom surveyor/dom layout created'); + domFilterer = vAPI.domFilterer; + pendingNodes.add(document.querySelectorAll( + '[id]:not(html):not(body),[class]:not(html):not(body)' + )); + surveyTimer.start(); + // https://github.com/uBlockOrigin/uBlock-issues/issues/1692 + // Look-up safe-only selectors to mitigate probability of + // html/body elements of erroneously being targeted. + const ids = [], classes = []; + if ( document.documentElement !== null ) { + idFromNode(document.documentElement, ids); + classesFromNode(document.documentElement, classes); + } + if ( document.body !== null ) { + idFromNode(document.body, ids); + classesFromNode(document.body, classes); + } + if ( ids.length !== 0 || classes.length !== 0 ) { + messaging.send('contentscript', { + what: 'retrieveGenericCosmeticSelectors', + hostname, + ids, classes, + exceptions: domFilterer.exceptions, + safeOnly: true, + }).then(response => { + surveyPhase3(response); + }); + } + //console.timeEnd('dom surveyor/dom layout created'); + }, + onDOMChanged: function(addedNodes) { + if ( addedNodes.length === 0 ) { return; } + //console.time('dom surveyor/dom layout changed'); + for ( const node of addedNodes ) { + pendingNodes.add([ node ]); + if ( node.firstElementChild === null ) { continue; } + pendingNodes.add(node.querySelectorAll( + '[id]:not(html):not(body),[class]:not(html):not(body)' + )); + } + if ( pendingNodes.hasNodes() ) { + surveyTimer.start(1); + } + //console.timeEnd('dom surveyor/dom layout changed'); + } + }; + + const start = function(details) { + if ( vAPI.domWatcher instanceof Object === false ) { return; } + hostname = details.hostname; + vAPI.domWatcher.addListener(domWatcherInterface); + }; + + vAPI.domSurveyor = { start }; +} + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +// vAPI.bootstrap: +// Bootstrapping allows all components of the content script +// to be launched if/when needed. + +{ + const bootstrapPhase2 = function() { + // This can happen on Firefox. For instance: + // https://github.com/gorhill/uBlock/issues/1893 + if ( window.location === null ) { return; } + if ( self.vAPI instanceof Object === false ) { return; } + + vAPI.messaging.send('contentscript', { + what: 'shouldRenderNoscriptTags', + }); + + if ( vAPI.domWatcher instanceof Object ) { + vAPI.domWatcher.start(); + } + + // Element picker works only in top window for now. + if ( + window !== window.top || + vAPI.domFilterer instanceof Object === false + ) { + return; + } + + // To be used by element picker/zapper. + vAPI.mouseClick = { x: -1, y: -1 }; + + const onMouseClick = function(ev) { + if ( ev.isTrusted === false ) { return; } + vAPI.mouseClick.x = ev.clientX; + vAPI.mouseClick.y = ev.clientY; + + // https://github.com/chrisaljoudi/uBlock/issues/1143 + // Find a link under the mouse, to try to avoid confusing new tabs + // as nuisance popups. + // https://github.com/uBlockOrigin/uBlock-issues/issues/777 + // Mind that href may not be a string. + const elem = ev.target.closest('a[href]'); + if ( elem === null || typeof elem.href !== 'string' ) { return; } + vAPI.messaging.send('contentscript', { + what: 'maybeGoodPopup', + url: elem.href || '', + }); + }; + + document.addEventListener('mousedown', onMouseClick, true); + + // https://github.com/gorhill/uMatrix/issues/144 + vAPI.shutdown.add(function() { + document.removeEventListener('mousedown', onMouseClick, true); + }); + }; + + // https://github.com/uBlockOrigin/uBlock-issues/issues/403 + // If there was a spurious port disconnection -- in which case the + // response is expressly set to `null`, rather than undefined or + // an object -- let's stay around, we may be given the opportunity + // to try bootstrapping again later. + + const bootstrapPhase1 = function(response) { + if ( response instanceof Object === false ) { return; } + + vAPI.bootstrap = undefined; + + // cosmetic filtering engine aka 'cfe' + const cfeDetails = response && response.specificCosmeticFilters; + if ( !cfeDetails || !cfeDetails.ready ) { + vAPI.domWatcher = vAPI.domCollapser = vAPI.domFilterer = + vAPI.domSurveyor = vAPI.domIsLoaded = null; + return; + } + + vAPI.domCollapser.start(); + + const { + noSpecificCosmeticFiltering, + noGenericCosmeticFiltering, + scriptlets, + } = response; + + vAPI.noSpecificCosmeticFiltering = noSpecificCosmeticFiltering; + vAPI.noGenericCosmeticFiltering = noGenericCosmeticFiltering; + + if ( noSpecificCosmeticFiltering && noGenericCosmeticFiltering ) { + vAPI.domFilterer = null; + vAPI.domSurveyor = null; + } else { + const domFilterer = vAPI.domFilterer = new vAPI.DOMFilterer(); + if ( noGenericCosmeticFiltering || cfeDetails.noDOMSurveying ) { + vAPI.domSurveyor = null; + } + domFilterer.exceptions = cfeDetails.exceptionFilters; + domFilterer.addCSS(cfeDetails.injectedCSS); + domFilterer.addProceduralSelectors(cfeDetails.proceduralFilters); + domFilterer.exceptCSSRules(cfeDetails.exceptedFilters); + } + + vAPI.userStylesheet.apply(); + + // Library of resources is located at: + // https://github.com/gorhill/uBlock/blob/master/assets/ublock/resources.txt + if ( scriptlets ) { + vAPI.injectScriptlet(document, scriptlets); + vAPI.injectedScripts = scriptlets; + } + + if ( vAPI.domSurveyor instanceof Object ) { + vAPI.domSurveyor.start(cfeDetails); + } + + // https://github.com/chrisaljoudi/uBlock/issues/587 + // If no filters were found, maybe the script was injected before + // uBlock's process was fully initialized. When this happens, pages + // won't be cleaned right after browser launch. + if ( + typeof document.readyState === 'string' && + document.readyState !== 'loading' + ) { + bootstrapPhase2(); + } else { + document.addEventListener( + 'DOMContentLoaded', + bootstrapPhase2, + { once: true } + ); + } + }; + + vAPI.bootstrap = function() { + vAPI.messaging.send('contentscript', { + what: 'retrieveContentScriptParameters', + url: vAPI.effectiveSelf.location.href, + }).then(response => { + bootstrapPhase1(response); + }); + }; +} + +// This starts bootstrap process. +vAPI.bootstrap(); + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +} +// <<<<<<<< end of HUGE-IF-BLOCK diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/contextmenu.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/contextmenu.js new file mode 100644 index 0000000..f3d9391 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/contextmenu.js @@ -0,0 +1,230 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +'use strict'; + +/******************************************************************************/ + +import µb from './background.js'; + +/******************************************************************************/ + +const contextMenu = (( ) => { + +/******************************************************************************/ + +if ( vAPI.contextMenu === undefined ) { + return { + update: function() {} + }; +} + +/******************************************************************************/ + +const onBlockElement = function(details, tab) { + if ( tab === undefined ) { return; } + if ( /^https?:\/\//.test(tab.url) === false ) { return; } + let tagName = details.tagName || ''; + let src = details.frameUrl || details.srcUrl || details.linkUrl || ''; + + if ( !tagName ) { + if ( typeof details.frameUrl === 'string' ) { + tagName = 'iframe'; + } else if ( typeof details.srcUrl === 'string' ) { + if ( details.mediaType === 'image' ) { + tagName = 'img'; + } else if ( details.mediaType === 'video' ) { + tagName = 'video'; + } else if ( details.mediaType === 'audio' ) { + tagName = 'audio'; + } + } else if ( typeof details.linkUrl === 'string' ) { + tagName = 'a'; + } + } + + µb.epickerArgs.mouse = true; + µb.elementPickerExec(tab.id, 0, `${tagName}\t${src}`); +}; + +/******************************************************************************/ + +const onBlockElementInFrame = function(details, tab) { + if ( tab === undefined ) { return; } + if ( /^https?:\/\//.test(details.frameUrl) === false ) { return; } + µb.epickerArgs.mouse = false; + µb.elementPickerExec(tab.id, details.frameId); +}; + +/******************************************************************************/ + +const onSubscribeToList = function(details) { + let parsedURL; + try { + parsedURL = new URL(details.linkUrl); + } + catch(ex) { + } + if ( parsedURL instanceof URL === false ) { return; } + const url = parsedURL.searchParams.get('location'); + if ( url === null ) { return; } + const title = parsedURL.searchParams.get('title') || '?'; + const hash = µb.selectedFilterLists.indexOf(parsedURL) !== -1 + ? '#subscribed' + : ''; + vAPI.tabs.open({ + url: + `/asset-viewer.html` + + `?url=${encodeURIComponent(url)}` + + `&title=${encodeURIComponent(title)}` + + `&subscribe=1${hash}`, + select: true, + }); +}; + +/******************************************************************************/ + +const onTemporarilyAllowLargeMediaElements = function(details, tab) { + if ( tab === undefined ) { return; } + const pageStore = µb.pageStoreFromTabId(tab.id); + if ( pageStore === null ) { return; } + pageStore.temporarilyAllowLargeMediaElements(true); +}; + +/******************************************************************************/ + +const onEntryClicked = function(details, tab) { + if ( details.menuItemId === 'uBlock0-blockElement' ) { + return onBlockElement(details, tab); + } + if ( details.menuItemId === 'uBlock0-blockElementInFrame' ) { + return onBlockElementInFrame(details, tab); + } + if ( details.menuItemId === 'uBlock0-blockResource' ) { + return onBlockElement(details, tab); + } + if ( details.menuItemId === 'uBlock0-subscribeToList' ) { + return onSubscribeToList(details); + } + if ( details.menuItemId === 'uBlock0-temporarilyAllowLargeMediaElements' ) { + return onTemporarilyAllowLargeMediaElements(details, tab); + } +}; + +/******************************************************************************/ + +const menuEntries = { + blockElement: { + id: 'uBlock0-blockElement', + title: vAPI.i18n('pickerContextMenuEntry'), + contexts: [ 'all' ], + }, + blockElementInFrame: { + id: 'uBlock0-blockElementInFrame', + title: vAPI.i18n('contextMenuBlockElementInFrame'), + contexts: [ 'frame' ], + }, + blockResource: { + id: 'uBlock0-blockResource', + title: vAPI.i18n('pickerContextMenuEntry'), + contexts: [ 'audio', 'frame', 'image', 'video' ], + }, + subscribeToList: { + id: 'uBlock0-subscribeToList', + title: vAPI.i18n('contextMenuSubscribeToList'), + contexts: [ 'link' ], + targetUrlPatterns: [ 'abp:*', 'https://subscribe.adblockplus.org/*' ], + }, + temporarilyAllowLargeMediaElements: { + id: 'uBlock0-temporarilyAllowLargeMediaElements', + title: vAPI.i18n('contextMenuTemporarilyAllowLargeMediaElements'), + contexts: [ 'all' ], + } +}; + +/******************************************************************************/ + +let currentBits = 0; + +const update = function(tabId = undefined) { + let newBits = 0; + if ( µb.userSettings.contextMenuEnabled && tabId !== undefined ) { + const pageStore = µb.pageStoreFromTabId(tabId); + if ( pageStore && pageStore.getNetFilteringSwitch() ) { + if ( pageStore.shouldApplySpecificCosmeticFilters(0) ) { + newBits |= 0b0001; + } else { + newBits |= 0b0010; + } + if ( pageStore.largeMediaCount !== 0 ) { + newBits |= 0b0100; + } + } + newBits |= 0b1000; + } + if ( newBits === currentBits ) { return; } + currentBits = newBits; + const usedEntries = []; + if ( newBits & 0b0001 ) { + usedEntries.push(menuEntries.blockElement); + usedEntries.push(menuEntries.blockElementInFrame); + } + if ( newBits & 0b0010 ) { + usedEntries.push(menuEntries.blockResource); + } + if ( newBits & 0b0100 ) { + usedEntries.push(menuEntries.temporarilyAllowLargeMediaElements); + } + if ( newBits & 0b1000 ) { + usedEntries.push(menuEntries.subscribeToList); + } + vAPI.contextMenu.setEntries(usedEntries, onEntryClicked); +}; + +/******************************************************************************/ + +// https://github.com/uBlockOrigin/uBlock-issues/issues/151 +// For unknown reasons, the currently active tab will not be successfully +// looked up after closing a window. + +vAPI.contextMenu.onMustUpdate = async function(tabId = undefined) { + if ( µb.userSettings.contextMenuEnabled === false ) { + return update(); + } + if ( tabId !== undefined ) { + return update(tabId); + } + const tab = await vAPI.tabs.getCurrent(); + if ( tab instanceof Object === false ) { return; } + update(tab.id); +}; + +return { update: vAPI.contextMenu.onMustUpdate }; + +/******************************************************************************/ + +})(); + +/******************************************************************************/ + +export default contextMenu; + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/cosmetic-filtering.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/cosmetic-filtering.js new file mode 100644 index 0000000..f3bbde0 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/cosmetic-filtering.js @@ -0,0 +1,1164 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +'use strict'; + +/******************************************************************************/ + +import './utils.js'; +import logger from './logger.js'; +import µb from './background.js'; + +import { + StaticExtFilteringHostnameDB, + StaticExtFilteringSessionDB, +} from './static-ext-filtering-db.js'; + +/******************************************************************************/ + +const cosmeticSurveyingMissCountMax = + parseInt(vAPI.localStorage.getItem('cosmeticSurveyingMissCountMax'), 10) || + 15; + +/******************************************************************************/ +/******************************************************************************/ + +const SelectorCacheEntry = class { + constructor() { + this.reset(); + } + + reset() { + this.cosmetic = new Set(); + this.cosmeticSurveyingMissCount = 0; + this.net = new Map(); + this.lastAccessTime = Date.now(); + return this; + } + + dispose() { + this.cosmetic = this.net = null; + if ( SelectorCacheEntry.junkyard.length < 25 ) { + SelectorCacheEntry.junkyard.push(this); + } + } + + addCosmetic(details) { + const selectors = details.selectors; + let i = selectors.length || 0; + // https://github.com/gorhill/uBlock/issues/2011 + // Avoiding seemingly pointless surveys only if they appear costly. + if ( details.first && i === 0 ) { + if ( (details.cost || 0) >= 80 ) { + this.cosmeticSurveyingMissCount += 1; + } + return; + } + this.cosmeticSurveyingMissCount = 0; + while ( i-- ) { + this.cosmetic.add(selectors[i]); + } + } + + addNet(selectors) { + if ( typeof selectors === 'string' ) { + this.addNetOne(selectors, Date.now()); + } else { + this.addNetMany(selectors, Date.now()); + } + // Net request-derived selectors: I limit the number of cached + // selectors, as I expect cases where the blocked net-requests + // are never the exact same URL. + if ( this.net.size < SelectorCacheEntry.netHighWaterMark ) { + return; + } + const dict = this.net; + const keys = Array.from(dict.keys()).sort(function(a, b) { + return dict.get(b) - dict.get(a); + }).slice(SelectorCacheEntry.netLowWaterMark); + let i = keys.length; + while ( i-- ) { + dict.delete(keys[i]); + } + } + + addNetOne(selector, now) { + this.net.set(selector, now); + } + + addNetMany(selectors, now) { + let i = selectors.length || 0; + while ( i-- ) { + this.net.set(selectors[i], now); + } + } + + add(details) { + this.lastAccessTime = Date.now(); + if ( details.type === 'cosmetic' ) { + this.addCosmetic(details); + } else { + this.addNet(details.selectors); + } + } + + // https://github.com/chrisaljoudi/uBlock/issues/420 + remove(type) { + this.lastAccessTime = Date.now(); + if ( type === undefined || type === 'cosmetic' ) { + this.cosmetic.clear(); + this.cosmeticSurveyingMissCount = 0; + } + if ( type === undefined || type === 'net' ) { + this.net.clear(); + } + } + + retrieveToArray(iterator, out) { + for ( let selector of iterator ) { + out.push(selector); + } + } + + retrieveToSet(iterator, out) { + for ( let selector of iterator ) { + out.add(selector); + } + } + + retrieve(type, out) { + this.lastAccessTime = Date.now(); + const iterator = type === 'cosmetic' ? this.cosmetic : this.net.keys(); + if ( Array.isArray(out) ) { + this.retrieveToArray(iterator, out); + } else { + this.retrieveToSet(iterator, out); + } + } + + static factory() { + const entry = SelectorCacheEntry.junkyard.pop(); + if ( entry ) { + return entry.reset(); + } + return new SelectorCacheEntry(); + } +}; + +SelectorCacheEntry.netLowWaterMark = 20; +SelectorCacheEntry.netHighWaterMark = 30; +SelectorCacheEntry.junkyard = []; + +/******************************************************************************/ +/******************************************************************************/ + +// Cosmetic filter family tree: +// +// Generic +// Low generic simple: class or id only +// Low generic complex: class or id + extra stuff after +// High generic: +// High-low generic: [alt="..."],[title="..."] +// High-medium generic: [href^="..."] +// High-high generic: everything else +// Specific +// Specific hostname +// Specific entity +// Generic filters can only be enforced once the main document is loaded. +// Specific filers can be enforced before the main document is loaded. + +const FilterContainer = function() { + this.reHasUnicode = /[^\x00-\x7F]/; + this.rePlainSelector = /^[#.][\w\\-]+/; + this.rePlainSelectorEscaped = /^[#.](?:\\[0-9A-Fa-f]+ |\\.|\w|-)+/; + this.rePlainSelectorEx = /^[^#.\[(]+([#.][\w-]+)|([#.][\w-]+)$/; + this.reEscapeSequence = /\\([0-9A-Fa-f]+ |.)/g; + this.reSimpleHighGeneric = /^(?:[a-z]*\[[^\]]+\]|\S+)$/; + this.reHighMedium = /^\[href\^="https?:\/\/([^"]{8})[^"]*"\]$/; + + this.selectorCache = new Map(); + this.selectorCachePruneDelay = 10 * 60 * 1000; // 10 minutes + this.selectorCacheAgeMax = 120 * 60 * 1000; // 120 minutes + this.selectorCacheCountMin = 25; + this.netSelectorCacheCountMax = SelectorCacheEntry.netHighWaterMark; + this.selectorCacheTimer = null; + + // specific filters + this.specificFilters = new StaticExtFilteringHostnameDB(2); + + // temporary filters + this.sessionFilterDB = new StaticExtFilteringSessionDB(); + + // low generic cosmetic filters, organized by id/class then simple/complex. + this.lowlyGeneric = Object.create(null); + this.lowlyGeneric.id = { + canonical: 'ids', + prefix: '#', + simple: new Set(), + complex: new Map() + }; + this.lowlyGeneric.cl = { + canonical: 'classes', + prefix: '.', + simple: new Set(), + complex: new Map() + }; + + // highly generic selectors sets + this.highlyGeneric = Object.create(null); + this.highlyGeneric.simple = { + canonical: 'highGenericHideSimple', + dict: new Set(), + str: '', + mru: new µb.MRUCache(16) + }; + this.highlyGeneric.complex = { + canonical: 'highGenericHideComplex', + dict: new Set(), + str: '', + mru: new µb.MRUCache(16) + }; + + // Short-lived: content is valid only during one function call. These + // is to prevent repeated allocation/deallocation overheads -- the + // constructors/destructors of javascript Set/Map is assumed to be costlier + // than just calling clear() on these. + this.$simpleSet = new Set(); + this.$complexSet = new Set(); + this.$specificSet = new Set(); + this.$exceptionSet = new Set(); + this.$proceduralSet = new Set(); + this.$dummySet = new Set(); + + this.reset(); +}; + +/******************************************************************************/ + +// Reset all, thus reducing to a minimum memory footprint of the context. + +FilterContainer.prototype.reset = function() { + this.frozen = false; + this.acceptedCount = 0; + this.discardedCount = 0; + this.duplicateBuster = new Set(); + + this.selectorCache.clear(); + if ( this.selectorCacheTimer !== null ) { + clearTimeout(this.selectorCacheTimer); + this.selectorCacheTimer = null; + } + + // whether there is at least one surveyor-based filter + this.needDOMSurveyor = false; + + // hostname, entity-based filters + this.specificFilters.clear(); + + // low generic cosmetic filters, organized by id/class then simple/complex. + this.lowlyGeneric.id.simple.clear(); + this.lowlyGeneric.id.complex.clear(); + this.lowlyGeneric.cl.simple.clear(); + this.lowlyGeneric.cl.complex.clear(); + + // highly generic selectors sets + this.highlyGeneric.simple.dict.clear(); + this.highlyGeneric.simple.str = ''; + this.highlyGeneric.simple.mru.reset(); + this.highlyGeneric.complex.dict.clear(); + this.highlyGeneric.complex.str = ''; + this.highlyGeneric.complex.mru.reset(); +}; + +/******************************************************************************/ + +FilterContainer.prototype.freeze = function() { + this.duplicateBuster.clear(); + this.specificFilters.collectGarbage(); + + this.needDOMSurveyor = + this.lowlyGeneric.id.simple.size !== 0 || + this.lowlyGeneric.id.complex.size !== 0 || + this.lowlyGeneric.cl.simple.size !== 0 || + this.lowlyGeneric.cl.complex.size !== 0; + + this.highlyGeneric.simple.str = Array.from(this.highlyGeneric.simple.dict).join(',\n'); + this.highlyGeneric.simple.mru.reset(); + this.highlyGeneric.complex.str = Array.from(this.highlyGeneric.complex.dict).join(',\n'); + this.highlyGeneric.complex.mru.reset(); + + this.frozen = true; +}; + +/******************************************************************************/ + +// https://github.com/gorhill/uBlock/issues/1668 +// The key must be literal: unescape escaped CSS before extracting key. +// It's an uncommon case, so it's best to unescape only when needed. + +FilterContainer.prototype.keyFromSelector = function(selector) { + let matches = this.rePlainSelector.exec(selector); + if ( matches === null ) { return; } + let key = matches[0]; + if ( key.indexOf('\\') === -1 ) { + return key; + } + matches = this.rePlainSelectorEscaped.exec(selector); + if ( matches === null ) { return; } + key = ''; + const escaped = matches[0]; + let beg = 0; + this.reEscapeSequence.lastIndex = 0; + for (;;) { + matches = this.reEscapeSequence.exec(escaped); + if ( matches === null ) { + return key + escaped.slice(beg); + } + key += escaped.slice(beg, matches.index); + beg = this.reEscapeSequence.lastIndex; + if ( matches[1].length === 1 ) { + key += matches[1]; + } else { + key += String.fromCharCode(parseInt(matches[1], 16)); + } + } +}; + +/******************************************************************************/ + +FilterContainer.prototype.compile = function(parser, writer) { + if ( parser.hasOptions() === false ) { + this.compileGenericSelector(parser, writer); + return true; + } + + // https://github.com/chrisaljoudi/uBlock/issues/151 + // Negated hostname means the filter applies to all non-negated hostnames + // of same filter OR globally if there is no non-negated hostnames. + let applyGlobally = true; + for ( const { hn, not, bad } of parser.extOptions() ) { + if ( bad ) { continue; } + if ( not === false ) { + applyGlobally = false; + } + this.compileSpecificSelector(parser, hn, not, writer); + } + if ( applyGlobally ) { + this.compileGenericSelector(parser, writer); + } + + return true; +}; + +/******************************************************************************/ + +FilterContainer.prototype.compileGenericSelector = function(parser, writer) { + if ( parser.isException() ) { + this.compileGenericUnhideSelector(parser, writer); + } else { + this.compileGenericHideSelector(parser, writer); + } +}; + +/******************************************************************************/ + +FilterContainer.prototype.compileGenericHideSelector = function( + parser, + writer +) { + const { raw, compiled } = parser.result; + if ( compiled === undefined ) { + const who = writer.properties.get('name') || '?'; + logger.writeOne({ + realm: 'message', + type: 'error', + text: `Invalid generic cosmetic filter in ${who}: ${raw}` + }); + return; + } + + writer.select('COSMETIC_FILTERS:GENERIC'); + + const type = compiled.charCodeAt(0); + let key; + + // Simple selector-based CSS rule: no need to test for whether the + // selector is valid, the regex took care of this. Most generic selector + // falls into that category: + // - ###ad-bigbox + // - ##.ads-bigbox + if ( type === 0x23 /* '#' */ ) { + key = this.keyFromSelector(compiled); + if ( key === compiled ) { + writer.push([ 0, key.slice(1) ]); + return; + } + } else if ( type === 0x2E /* '.' */ ) { + key = this.keyFromSelector(compiled); + if ( key === compiled ) { + writer.push([ 2, key.slice(1) ]); + return; + } + } + + // Invalid cosmetic filter, possible reasons: + // - Bad syntax + // - Procedural filters (can't be generic): the compiled version of + // a procedural selector is NEVER equal to its raw version. + // https://github.com/uBlockOrigin/uBlock-issues/issues/464 + // Pseudoclass-based selectors can be compiled, but are also valid + // plain selectors. + // https://github.com/uBlockOrigin/uBlock-issues/issues/131 + // Support generic procedural filters as per advanced settings. + // TODO: prevent double compilation. + if ( compiled !== raw ) { + if ( µb.hiddenSettings.allowGenericProceduralFilters === true ) { + return this.compileSpecificSelector(parser, '', false, writer); + } + const who = writer.properties.get('name') || '?'; + logger.writeOne({ + realm: 'message', + type: 'error', + text: `Invalid generic cosmetic filter in ${who}: ##${raw}` + }); + return; + } + + // Complex selector-based CSS rule: + // - ###tads + div + .c + // - ##.rscontainer > .ellip + if ( key !== undefined ) { + writer.push([ + type === 0x23 /* '#' */ ? 1 : 3, + key.slice(1), + compiled + ]); + return; + } + + // https://github.com/gorhill/uBlock/issues/909 + // Anything which contains a plain id/class selector can be classified + // as a low generic cosmetic filter. + const matches = this.rePlainSelectorEx.exec(compiled); + if ( matches !== null ) { + const key = matches[1] || matches[2]; + writer.push([ + key.charCodeAt(0) === 0x23 /* '#' */ ? 1 : 3, + key.slice(1), + compiled + ]); + return; + } + + // Pass this point, we are dealing with highly-generic cosmetic filters. + // + // For efficiency purpose, we will distinguish between simple and complex + // selectors. + + if ( this.reSimpleHighGeneric.test(compiled) ) { + writer.push([ 4 /* simple */, compiled ]); + } else { + writer.push([ 5 /* complex */, compiled ]); + } +}; + +/******************************************************************************/ + +FilterContainer.prototype.compileGenericUnhideSelector = function( + parser, + writer +) { + // Procedural cosmetic filters are acceptable as generic exception filters. + const { raw, compiled } = parser.result; + if ( compiled === undefined ) { + const who = writer.properties.get('name') || '?'; + logger.writeOne({ + realm: 'message', + type: 'error', + text: `Invalid cosmetic filter in ${who}: #@#${raw}` + }); + return; + } + + writer.select('COSMETIC_FILTERS:SPECIFIC'); + + // https://github.com/chrisaljoudi/uBlock/issues/497 + // All generic exception filters are stored as hostname-based filter + // whereas the hostname is the empty string (which matches all + // hostnames). No distinction is made between declarative and + // procedural selectors, since they really exist only to cancel + // out other cosmetic filters. + writer.push([ 8, '', 0b001, compiled ]); +}; + +/******************************************************************************/ + +FilterContainer.prototype.compileSpecificSelector = function( + parser, + hostname, + not, + writer +) { + const { raw, compiled, exception } = parser.result; + if ( compiled === undefined ) { + const who = writer.properties.get('name') || '?'; + logger.writeOne({ + realm: 'message', + type: 'error', + text: `Invalid cosmetic filter in ${who}: ##${raw}` + }); + return; + } + + writer.select('COSMETIC_FILTERS:SPECIFIC'); + + // https://github.com/chrisaljoudi/uBlock/issues/145 + let unhide = exception ? 1 : 0; + if ( not ) { unhide ^= 1; } + + let kind = 0; + if ( unhide === 1 ) { + kind |= 0b001; // Exception + } + if ( compiled.charCodeAt(0) === 0x7B /* '{' */ ) { + kind |= 0b010; // Procedural + } + if ( hostname === '*' ) { + kind |= 0b100; // Applies everywhere + } + + writer.push([ 8, hostname, kind, compiled ]); +}; + +/******************************************************************************/ + +FilterContainer.prototype.compileTemporary = function(parser) { + return { + session: this.sessionFilterDB, + selector: parser.result.compiled, + }; +}; + +/******************************************************************************/ + +FilterContainer.prototype.fromCompiledContent = function(reader, options) { + if ( options.skipCosmetic ) { + this.skipCompiledContent(reader, 'SPECIFIC'); + this.skipCompiledContent(reader, 'GENERIC'); + return; + } + + // Specific cosmetic filter section + reader.select('COSMETIC_FILTERS:SPECIFIC'); + while ( reader.next() ) { + this.acceptedCount += 1; + const fingerprint = reader.fingerprint(); + if ( this.duplicateBuster.has(fingerprint) ) { + this.discardedCount += 1; + continue; + } + this.duplicateBuster.add(fingerprint); + const args = reader.args(); + switch ( args[0] ) { + // hash, example.com, .promoted-tweet + // hash, example.*, .promoted-tweet + // + // https://github.com/uBlockOrigin/uBlock-issues/issues/803 + // Handle specific filters meant to apply everywhere, i.e. selectors + // not to be injected conditionally through the DOM surveyor. + // hash, *, .promoted-tweet + case 8: + if ( args[2] === 0b100 ) { + if ( this.reSimpleHighGeneric.test(args[3]) ) + this.highlyGeneric.simple.dict.add(args[3]); + else { + this.highlyGeneric.complex.dict.add(args[3]); + } + break; + } + this.specificFilters.store(args[1], args[2] & 0b011, args[3]); + break; + default: + this.discardedCount += 1; + break; + } + } + + if ( options.skipGenericCosmetic ) { + this.skipCompiledContent(reader, 'GENERIC'); + return; + } + + // Generic cosmetic filter section + reader.select('COSMETIC_FILTERS:GENERIC'); + while ( reader.next() ) { + this.acceptedCount += 1; + const fingerprint = reader.fingerprint(); + if ( this.duplicateBuster.has(fingerprint) ) { + this.discardedCount += 1; + continue; + } + this.duplicateBuster.add(fingerprint); + const args = reader.args(); + switch ( args[0] ) { + // low generic, simple + case 0: // #AdBanner + case 2: { // .largeAd + const db = args[0] === 0 ? this.lowlyGeneric.id : this.lowlyGeneric.cl; + const bucket = db.complex.get(args[1]); + if ( bucket === undefined ) { + db.simple.add(args[1]); + } else if ( Array.isArray(bucket) ) { + bucket.push(db.prefix + args[1]); + } else { + db.complex.set(args[1], [ bucket, db.prefix + args[1] ]); + } + break; + } + // low generic, complex + case 1: // #tads + div + .c + case 3: { // .Mpopup + #Mad > #MadZone + const db = args[0] === 1 ? this.lowlyGeneric.id : this.lowlyGeneric.cl; + const bucket = db.complex.get(args[1]); + if ( bucket === undefined ) { + if ( db.simple.has(args[1]) ) { + db.complex.set(args[1], [ db.prefix + args[1], args[2] ]); + } else { + db.complex.set(args[1], args[2]); + db.simple.add(args[1]); + } + } else if ( Array.isArray(bucket) ) { + bucket.push(args[2]); + } else { + db.complex.set(args[1], [ bucket, args[2] ]); + } + break; + } + // High-high generic hide/simple selectors + // div[id^="allo"] + case 4: + this.highlyGeneric.simple.dict.add(args[1]); + break; + // High-high generic hide/complex selectors + // div[id^="allo"] > span + case 5: + this.highlyGeneric.complex.dict.add(args[1]); + break; + default: + this.discardedCount += 1; + break; + } + } +}; + +/******************************************************************************/ + +FilterContainer.prototype.skipCompiledContent = function(reader, sectionId) { + reader.select(`COSMETIC_FILTERS:${sectionId}`); + while ( reader.next() ) { + this.acceptedCount += 1; + this.discardedCount += 1; + } +}; + +/******************************************************************************/ + +FilterContainer.prototype.toSelfie = function() { + return { + acceptedCount: this.acceptedCount, + discardedCount: this.discardedCount, + specificFilters: this.specificFilters.toSelfie(), + lowlyGenericSID: Array.from(this.lowlyGeneric.id.simple), + lowlyGenericCID: Array.from(this.lowlyGeneric.id.complex), + lowlyGenericSCL: Array.from(this.lowlyGeneric.cl.simple), + lowlyGenericCCL: Array.from(this.lowlyGeneric.cl.complex), + highSimpleGenericHideArray: Array.from(this.highlyGeneric.simple.dict), + highComplexGenericHideArray: Array.from(this.highlyGeneric.complex.dict), + }; +}; + +/******************************************************************************/ + +FilterContainer.prototype.fromSelfie = function(selfie) { + this.acceptedCount = selfie.acceptedCount; + this.discardedCount = selfie.discardedCount; + this.specificFilters.fromSelfie(selfie.specificFilters); + this.lowlyGeneric.id.simple = new Set(selfie.lowlyGenericSID); + this.lowlyGeneric.id.complex = new Map(selfie.lowlyGenericCID); + this.lowlyGeneric.cl.simple = new Set(selfie.lowlyGenericSCL); + this.lowlyGeneric.cl.complex = new Map(selfie.lowlyGenericCCL); + this.highlyGeneric.simple.dict = new Set(selfie.highSimpleGenericHideArray); + this.highlyGeneric.simple.str = selfie.highSimpleGenericHideArray.join(',\n'); + this.highlyGeneric.complex.dict = new Set(selfie.highComplexGenericHideArray); + this.highlyGeneric.complex.str = selfie.highComplexGenericHideArray.join(',\n'); + this.needDOMSurveyor = + selfie.lowlyGenericSID.length !== 0 || + selfie.lowlyGenericCID.length !== 0 || + selfie.lowlyGenericSCL.length !== 0 || + selfie.lowlyGenericCCL.length !== 0; + this.frozen = true; +}; + +/******************************************************************************/ + +FilterContainer.prototype.triggerSelectorCachePruner = function() { + // Of interest: http://fitzgeraldnick.com/weblog/40/ + // http://googlecode.blogspot.ca/2009/07/gmail-for-mobile-html5-series-using.html + if ( this.selectorCacheTimer === null ) { + this.selectorCacheTimer = vAPI.setTimeout( + this.pruneSelectorCacheAsync.bind(this), + this.selectorCachePruneDelay + ); + } +}; + +/******************************************************************************/ + +FilterContainer.prototype.addToSelectorCache = function(details) { + const hostname = details.hostname; + if ( typeof hostname !== 'string' || hostname === '' ) { return; } + const selectors = details.selectors; + if ( Array.isArray(selectors) === false ) { return; } + let entry = this.selectorCache.get(hostname); + if ( entry === undefined ) { + entry = SelectorCacheEntry.factory(); + this.selectorCache.set(hostname, entry); + if ( this.selectorCache.size > this.selectorCacheCountMin ) { + this.triggerSelectorCachePruner(); + } + } + entry.add(details); +}; + +/******************************************************************************/ + +FilterContainer.prototype.removeFromSelectorCache = function( + targetHostname = '*', + type = undefined +) { + let targetHostnameLength = targetHostname.length; + for ( let entry of this.selectorCache ) { + let hostname = entry[0]; + let item = entry[1]; + if ( targetHostname !== '*' ) { + if ( hostname.endsWith(targetHostname) === false ) { continue; } + if ( + hostname.length !== targetHostnameLength && + hostname.charAt(hostname.length - targetHostnameLength - 1) !== '.' + ) { + continue; + } + } + item.remove(type); + } +}; + +/******************************************************************************/ + +FilterContainer.prototype.retrieveFromSelectorCache = function( + hostname, + type, + out +) { + let entry = this.selectorCache.get(hostname); + if ( entry !== undefined ) { + entry.retrieve(type, out); + } +}; + +/******************************************************************************/ + +FilterContainer.prototype.pruneSelectorCacheAsync = function() { + this.selectorCacheTimer = null; + if ( this.selectorCache.size <= this.selectorCacheCountMin ) { return; } + let cache = this.selectorCache; + // Sorted from most-recently-used to least-recently-used, because + // we loop beginning at the end below. + // We can't avoid sorting because we have to keep a minimum number of + // entries, and these entries should always be the most-recently-used. + let hostnames = Array.from(cache.keys()) + .sort(function(a, b) { + return cache.get(b).lastAccessTime - + cache.get(a).lastAccessTime; + }) + .slice(this.selectorCacheCountMin); + let obsolete = Date.now() - this.selectorCacheAgeMax, + i = hostnames.length; + while ( i-- ) { + let hostname = hostnames[i]; + let entry = cache.get(hostname); + if ( entry.lastAccessTime > obsolete ) { break; } + // console.debug('pruneSelectorCacheAsync: flushing "%s"', hostname); + entry.dispose(); + cache.delete(hostname); + } + if ( cache.size > this.selectorCacheCountMin ) { + this.triggerSelectorCachePruner(); + } +}; + +/******************************************************************************/ + +FilterContainer.prototype.getSession = function() { + return this.sessionFilterDB; +}; + +/******************************************************************************/ + +FilterContainer.prototype.retrieveGenericSelectors = function(request) { + if ( this.acceptedCount === 0 ) { return; } + if ( !request.ids && !request.classes ) { return; } + + const { safeOnly = false } = request; + //console.time('cosmeticFilteringEngine.retrieveGenericSelectors'); + + const simpleSelectors = this.$simpleSet; + const complexSelectors = this.$complexSet; + + const cacheEntry = this.selectorCache.get(request.hostname); + const previousHits = cacheEntry && cacheEntry.cosmetic || this.$dummySet; + + for ( const type in this.lowlyGeneric ) { + const entry = this.lowlyGeneric[type]; + const selectors = request[entry.canonical]; + if ( Array.isArray(selectors) === false ) { continue; } + for ( const identifier of selectors ) { + if ( entry.simple.has(identifier) === false ) { continue; } + const bucket = entry.complex.get(identifier); + if ( typeof bucket === 'string' ) { + if ( previousHits.has(bucket) ) { continue; } + complexSelectors.add(bucket); + continue; + } + const simpleSelector = entry.prefix + identifier; + if ( Array.isArray(bucket) ) { + for ( const complexSelector of bucket ) { + if ( previousHits.has(complexSelector) ) { continue; } + if ( safeOnly && complexSelector === simpleSelector ) { continue; } + complexSelectors.add(complexSelector); + } + continue; + } + if ( previousHits.has(simpleSelector) ) { continue; } + if ( safeOnly ) { continue; } + simpleSelectors.add(simpleSelector); + } + } + + // Apply exceptions: it is the responsibility of the caller to provide + // the exceptions to be applied. + const excepted = []; + if ( Array.isArray(request.exceptions) ) { + for ( const exception of request.exceptions ) { + if ( + simpleSelectors.delete(exception) || + complexSelectors.delete(exception) + ) { + excepted.push(exception); + } + } + } + + if ( + simpleSelectors.size === 0 && + complexSelectors.size === 0 && + excepted.length === 0 + ) { + return; + } + + const out = { injectedCSS: '', excepted, }; + + const injected = []; + if ( simpleSelectors.size !== 0 ) { + injected.push(...simpleSelectors); + simpleSelectors.clear(); + } + if ( complexSelectors.size !== 0 ) { + injected.push(...complexSelectors); + complexSelectors.clear(); + } + + // Cache and inject looked-up low generic cosmetic filters. + if ( injected.length === 0 ) { return out; } + + if ( typeof request.hostname === 'string' && request.hostname !== '' ) { + this.addToSelectorCache({ + cost: request.surveyCost || 0, + hostname: request.hostname, + selectors: injected, + type: 'cosmetic', + }); + } + + out.injectedCSS = `${injected.join(',\n')}\n{display:none!important;}`; + vAPI.tabs.insertCSS(request.tabId, { + code: out.injectedCSS, + frameId: request.frameId, + matchAboutBlank: true, + runAt: 'document_start', + }); + + //console.timeEnd('cosmeticFilteringEngine.retrieveGenericSelectors'); + + return out; +}; + +/******************************************************************************/ + +FilterContainer.prototype.retrieveSpecificSelectors = function( + request, + options +) { + const hostname = request.hostname; + const cacheEntry = this.selectorCache.get(hostname); + + // https://github.com/chrisaljoudi/uBlock/issues/587 + // out.ready will tell the content script the cosmetic filtering engine is + // up and ready. + + // https://github.com/chrisaljoudi/uBlock/issues/497 + // Generic exception filters are to be applied on all pages. + + const out = { + ready: this.frozen, + hostname: hostname, + domain: request.domain, + exceptionFilters: [], + exceptedFilters: [], + noDOMSurveying: this.needDOMSurveyor === false, + }; + const injectedCSS = []; + + if ( + options.noSpecificCosmeticFiltering !== true || + options.noGenericCosmeticFiltering !== true + ) { + const injectedHideFilters = []; + const specificSet = this.$specificSet; + const proceduralSet = this.$proceduralSet; + const exceptionSet = this.$exceptionSet; + const dummySet = this.$dummySet; + + // Cached cosmetic filters: these are always declarative. + if ( cacheEntry !== undefined ) { + cacheEntry.retrieve('cosmetic', specificSet); + if ( out.noDOMSurveying === false ) { + out.noDOMSurveying = cacheEntry.cosmeticSurveyingMissCount > + cosmeticSurveyingMissCountMax; + } + } + + // Retrieve temporary filters + if ( this.sessionFilterDB.isNotEmpty ) { + this.sessionFilterDB.retrieve([ null, exceptionSet ]); + } + + // Retrieve filters with a non-empty hostname + this.specificFilters.retrieve( + hostname, + options.noSpecificCosmeticFiltering !== true + ? [ specificSet, exceptionSet, proceduralSet, exceptionSet ] + : [ dummySet, exceptionSet ], + 1 + ); + // Retrieve filters with an empty hostname + this.specificFilters.retrieve( + hostname, + options.noGenericCosmeticFiltering !== true + ? [ specificSet, exceptionSet, proceduralSet, exceptionSet ] + : [ dummySet, exceptionSet ], + 2 + ); + // Retrieve filters with a non-empty entity + if ( request.entity !== '' ) { + this.specificFilters.retrieve( + `${hostname.slice(0, -request.domain.length)}${request.entity}`, + options.noSpecificCosmeticFiltering !== true + ? [ specificSet, exceptionSet, proceduralSet, exceptionSet ] + : [ dummySet, exceptionSet ], + 1 + ); + } + + if ( exceptionSet.size !== 0 ) { + out.exceptionFilters = Array.from(exceptionSet); + for ( const exception of exceptionSet ) { + if ( + specificSet.delete(exception) || + proceduralSet.delete(exception) + ) { + out.exceptedFilters.push(exception); + } + } + } + + if ( specificSet.size !== 0 ) { + injectedHideFilters.push(Array.from(specificSet).join(',\n')); + } + + // Some procedural filters are really declarative cosmetic filters, so + // we extract and inject them immediately. + if ( proceduralSet.size !== 0 ) { + for ( const json of proceduralSet ) { + const pfilter = JSON.parse(json); + if ( pfilter.tasks === undefined ) { + const { action } = pfilter; + if ( action !== undefined && action[0] === ':style' ) { + injectedCSS.push(`${pfilter.selector}\n{${action[1]}}`); + proceduralSet.delete(json); + continue; + } + } + } + if ( proceduralSet.size !== 0 ) { + out.proceduralFilters = Array.from(proceduralSet); + } + } + + // Highly generic cosmetic filters: sent once along with specific ones. + // A most-recent-used cache is used to skip computing the resulting set + // of high generics for a given set of exceptions. + // The resulting set of high generics is stored as a string, ready to + // be used as-is by the content script. The string is stored + // indirectly in the mru cache: this is to prevent duplication of the + // string in memory, which I have observed occurs when the string is + // stored directly as a value in a Map. + if ( options.noGenericCosmeticFiltering !== true ) { + const exceptionSetHash = out.exceptionFilters.join(); + for ( const key in this.highlyGeneric ) { + const entry = this.highlyGeneric[key]; + let str = entry.mru.lookup(exceptionSetHash); + if ( str === undefined ) { + str = { s: entry.str, excepted: [] }; + let genericSet = entry.dict; + let hit = false; + for ( const exception of exceptionSet ) { + if ( (hit = genericSet.has(exception)) ) { break; } + } + if ( hit ) { + genericSet = new Set(entry.dict); + for ( const exception of exceptionSet ) { + if ( genericSet.delete(exception) ) { + str.excepted.push(exception); + } + } + str.s = Array.from(genericSet).join(',\n'); + } + entry.mru.add(exceptionSetHash, str); + } + if ( str.excepted.length !== 0 ) { + out.exceptedFilters.push(...str.excepted); + } + if ( str.s.length !== 0 ) { + injectedHideFilters.push(str.s); + } + } + } + + if ( injectedHideFilters.length !== 0 ) { + injectedCSS.push( + `${injectedHideFilters.join(',\n')}\n{display:none!important;}` + ); + } + + // Important: always clear used registers before leaving. + specificSet.clear(); + proceduralSet.clear(); + exceptionSet.clear(); + dummySet.clear(); + } + + const details = { + code: '', + frameId: request.frameId, + matchAboutBlank: true, + runAt: 'document_start', + }; + + // Inject all declarative-based filters as a single stylesheet. + if ( injectedCSS.length !== 0 ) { + out.injectedCSS = injectedCSS.join('\n\n'); + details.code = out.injectedCSS; + if ( request.tabId !== undefined ) { + vAPI.tabs.insertCSS(request.tabId, details); + } + } + + // CSS selectors for collapsible blocked elements + if ( cacheEntry ) { + const networkFilters = []; + cacheEntry.retrieve('net', networkFilters); + if ( networkFilters.length !== 0 ) { + details.code = networkFilters.join('\n') + '\n{display:none!important;}'; + if ( request.tabId !== undefined ) { + vAPI.tabs.insertCSS(request.tabId, details); + } + } + } + + return out; +}; + +/******************************************************************************/ + +FilterContainer.prototype.getFilterCount = function() { + return this.acceptedCount - this.discardedCount; +}; + +/******************************************************************************/ + +FilterContainer.prototype.dump = function() { + let genericCount = 0; + for ( const i of [ 'simple', 'complex' ] ) { + for ( const j of [ 'id', 'cl' ] ) { + genericCount += this.lowlyGeneric[j][i].size; + } + } + return [ + 'Cosmetic Filtering Engine internals:', + `specific: ${this.specificFilters.size}`, + `generic: ${genericCount}`, + `+ lowly.id: ${this.lowlyGeneric.id.simple.size + this.lowlyGeneric.id.complex.size}`, + ` + simple: ${this.lowlyGeneric.id.simple.size}`, + ...Array.from(this.lowlyGeneric.id.simple).map(a => ` ###${a}`), + ` + complex: ${this.lowlyGeneric.id.complex.size}`, + ...Array.from(this.lowlyGeneric.id.complex.values()).map(a => ` ##${a}`), + `+ lowly.class: ${this.lowlyGeneric.cl.simple.size + this.lowlyGeneric.cl.complex.size}`, + ` + simple: ${this.lowlyGeneric.cl.simple.size}`, + ...Array.from(this.lowlyGeneric.cl.simple).map(a => ` ##.${a}`), + ` + complex: ${this.lowlyGeneric.cl.complex.size}`, + ...Array.from(this.lowlyGeneric.cl.complex.values()).map(a => ` ##${a}`), + `+ highly: ${this.highlyGeneric.simple.dict.size + this.highlyGeneric.complex.dict.size}`, + ` + highly.simple: ${this.highlyGeneric.simple.dict.size}`, + ...Array.from(this.highlyGeneric.simple.dict).map(a => ` ##${a}`), + ` + highly.complex: ${this.highlyGeneric.complex.dict.size}`, + ...Array.from(this.highlyGeneric.complex.dict).map(a => ` ##${a}`), + ].join('\n'); +}; + +/******************************************************************************/ + +const cosmeticFilteringEngine = new FilterContainer(); + +export default cosmeticFilteringEngine; + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/dashboard-common.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/dashboard-common.js new file mode 100644 index 0000000..1df07a9 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/dashboard-common.js @@ -0,0 +1,215 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +/* global uDom */ + +'use strict'; + +/******************************************************************************/ + +self.uBlockDashboard = self.uBlockDashboard || {}; + +/******************************************************************************/ + +// Helper for client panes: +// Remove literal duplicate lines from a set based on another set. + +self.uBlockDashboard.mergeNewLines = function(text, newText) { + // Step 1: build dictionary for existing lines. + const fromDict = new Map(); + let lineBeg = 0; + let textEnd = text.length; + while ( lineBeg < textEnd ) { + let lineEnd = text.indexOf('\n', lineBeg); + if ( lineEnd === -1 ) { + lineEnd = text.indexOf('\r', lineBeg); + if ( lineEnd === -1 ) { + lineEnd = textEnd; + } + } + const line = text.slice(lineBeg, lineEnd).trim(); + lineBeg = lineEnd + 1; + if ( line.length === 0 ) { continue; } + const hash = line.slice(0, 8); + const bucket = fromDict.get(hash); + if ( bucket === undefined ) { + fromDict.set(hash, line); + } else if ( typeof bucket === 'string' ) { + fromDict.set(hash, [ bucket, line ]); + } else /* if ( Array.isArray(bucket) ) */ { + bucket.push(line); + } + } + + // Step 2: use above dictionary to filter out duplicate lines. + const out = [ '' ]; + lineBeg = 0; + textEnd = newText.length; + while ( lineBeg < textEnd ) { + let lineEnd = newText.indexOf('\n', lineBeg); + if ( lineEnd === -1 ) { + lineEnd = newText.indexOf('\r', lineBeg); + if ( lineEnd === -1 ) { + lineEnd = textEnd; + } + } + const line = newText.slice(lineBeg, lineEnd).trim(); + lineBeg = lineEnd + 1; + if ( line.length === 0 ) { + if ( out[out.length - 1] !== '' ) { + out.push(''); + } + continue; + } + const bucket = fromDict.get(line.slice(0, 8)); + if ( bucket === undefined ) { + out.push(line); + continue; + } + if ( typeof bucket === 'string' && line !== bucket ) { + out.push(line); + continue; + } + if ( bucket.indexOf(line) === -1 ) { + out.push(line); + /* continue; */ + } + } + + const append = out.join('\n').trim(); + if ( text !== '' && append !== '' ) { + text += '\n\n'; + } + return text + append; +}; + +/******************************************************************************/ + +self.uBlockDashboard.dateNowToSensibleString = function() { + const now = new Date(Date.now() - (new Date()).getTimezoneOffset() * 60000); + return now.toISOString().replace(/\.\d+Z$/, '') + .replace(/:/g, '.') + .replace('T', '_'); +}; + +/******************************************************************************/ + +self.uBlockDashboard.patchCodeMirrorEditor = (function() { + let grabFocusTimer; + let grabFocusTarget; + + const grabFocus = function() { + grabFocusTarget.focus(); + grabFocusTimer = grabFocusTarget = undefined; + }; + const grabFocusAsync = function(cm) { + grabFocusTarget = cm; + if ( grabFocusTimer === undefined ) { + grabFocusTimer = vAPI.setTimeout(grabFocus, 1); + } + }; + + // https://github.com/gorhill/uBlock/issues/3646 + const patchSelectAll = function(cm, details) { + var vp = cm.getViewport(); + if ( details.ranges.length !== 1 ) { return; } + var range = details.ranges[0], + lineFrom = range.anchor.line, + lineTo = range.head.line; + if ( lineTo === lineFrom ) { return; } + if ( range.head.ch !== 0 ) { lineTo += 1; } + if ( lineFrom !== vp.from || lineTo !== vp.to ) { return; } + details.update([ + { + anchor: { line: 0, ch: 0 }, + head: { line: cm.lineCount(), ch: 0 } + } + ]); + grabFocusAsync(cm); + }; + + let lastGutterClick = 0; + let lastGutterLine = 0; + + const onGutterClicked = function(cm, line, gutter) { + if ( gutter !== 'CodeMirror-linenumbers' ) { return; } + grabFocusAsync(cm); + const delta = Date.now() - lastGutterClick; + // Single click + if ( delta >= 500 || line !== lastGutterLine ) { + cm.setSelection( + { line, ch: 0 }, + { line: line + 1, ch: 0 } + ); + lastGutterClick = Date.now(); + lastGutterLine = line; + return; + } + // Double click: select fold-able block or all + let lineFrom = 0; + let lineTo = cm.lineCount(); + const foldFn = cm.getHelper({ line, ch: 0 }, 'fold'); + if ( foldFn instanceof Function ) { + const range = foldFn(cm, { line, ch: 0 }); + if ( range !== undefined ) { + lineFrom = range.from.line; + lineTo = range.to.line + 1; + } + } + cm.setSelection( + { line: lineFrom, ch: 0 }, + { line: lineTo, ch: 0 }, + { scroll: false } + ); + lastGutterClick = 0; + }; + + return function(cm) { + if ( cm.options.inputStyle === 'contenteditable' ) { + cm.on('beforeSelectionChange', patchSelectAll); + } + cm.on('gutterClick', onGutterClicked); + }; +})(); + +/******************************************************************************/ + +self.uBlockDashboard.openOrSelectPage = function(url, options = {}) { + let ev; + if ( url instanceof MouseEvent ) { + ev = url; + url = ev.target.getAttribute('href'); + } + const details = Object.assign({ url, select: true, index: -1 }, options); + vAPI.messaging.send('default', { + what: 'gotoURL', + details, + }); + if ( ev ) { + ev.preventDefault(); + } +}; + +/******************************************************************************/ + +// Open links in the proper window +uDom('a').attr('target', '_blank'); +uDom('a[href*="dashboard.html"]').attr('target', '_parent'); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/dashboard.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/dashboard.js new file mode 100644 index 0000000..f391b7c --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/dashboard.js @@ -0,0 +1,155 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +/* global uDom */ + +'use strict'; + +/******************************************************************************/ + +{ +// >>>>> start of local scope + +/******************************************************************************/ + +const discardUnsavedData = function(synchronous = false) { + const paneFrame = document.getElementById('iframe'); + const paneWindow = paneFrame.contentWindow; + if ( + typeof paneWindow.hasUnsavedData !== 'function' || + paneWindow.hasUnsavedData() === false + ) { + return true; + } + + if ( synchronous ) { + return false; + } + + return new Promise(resolve => { + const modal = uDom.nodeFromId('unsavedWarning'); + modal.classList.add('on'); + modal.focus(); + + const onDone = status => { + modal.classList.remove('on'); + document.removeEventListener('click', onClick, true); + resolve(status); + }; + + const onClick = ev => { + const target = ev.target; + if ( target.matches('[data-i18n="dashboardUnsavedWarningStay"]') ) { + return onDone(false); + } + if ( target.matches('[data-i18n="dashboardUnsavedWarningIgnore"]') ) { + return onDone(true); + } + if ( modal.querySelector('[data-i18n="dashboardUnsavedWarning"]').contains(target) ) { + return; + } + onDone(false); + }; + + document.addEventListener('click', onClick, true); + }); +}; + +const loadDashboardPanel = function(pane, first) { + const tabButton = uDom.nodeFromSelector(`[data-pane="${pane}"]`); + if ( tabButton === null || tabButton.classList.contains('selected') ) { + return; + } + const loadPane = ( ) => { + self.location.replace(`#${pane}`); + uDom('.tabButton.selected').toggleClass('selected', false); + tabButton.classList.add('selected'); + tabButton.scrollIntoView(); + uDom.nodeFromId('iframe').contentWindow.location.replace(pane); + if ( pane !== 'no-dashboard.html' ) { + vAPI.localStorage.setItem('dashboardLastVisitedPane', pane); + } + }; + if ( first ) { + return loadPane(); + } + const r = discardUnsavedData(); + if ( r === false ) { return; } + if ( r === true ) { + return loadPane(); + } + r.then(status => { + if ( status === false ) { return; } + loadPane(); + }); +}; + +const onTabClickHandler = function(ev) { + loadDashboardPanel(ev.target.getAttribute('data-pane')); +}; + +if ( self.location.hash.slice(1) === 'no-dashboard.html' ) { + document.body.classList.add('noDashboard'); +} + +(async ( ) => { + const results = await Promise.all([ + // https://github.com/uBlockOrigin/uBlock-issues/issues/106 + vAPI.messaging.send('dashboard', { what: 'dashboardConfig' }), + vAPI.localStorage.getItemAsync('dashboardLastVisitedPane'), + ]); + + { + const details = results[0] || {}; + document.body.classList.toggle( + 'canUpdateShortcuts', + details.canUpdateShortcuts === true + ); + if ( details.noDashboard ) { + self.location.hash = '#no-dashboard.html'; + document.body.classList.add('noDashboard'); + } else if ( self.location.hash === '#no-dashboard.html' ) { + self.location.hash = ''; + } + } + + { + let pane = results[1] || null; + if ( self.location.hash !== '' ) { + pane = self.location.hash.slice(1) || null; + } + loadDashboardPanel(pane !== null ? pane : 'settings.html', true); + + uDom('.tabButton').on('click', onTabClickHandler); + + // https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event + window.addEventListener('beforeunload', ( ) => { + if ( discardUnsavedData(true) ) { return; } + event.preventDefault(); + event.returnValue = ''; + }); + } +})(); + +/******************************************************************************/ + +// <<<<< end of local scope +} diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/devtools.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/devtools.js new file mode 100644 index 0000000..e4346a3 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/devtools.js @@ -0,0 +1,173 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +/* global CodeMirror, uDom, uBlockDashboard */ + +'use strict'; + +/******************************************************************************/ + +const reFoldable = /^ *(?=\+ \S)/; + +/******************************************************************************/ + +CodeMirror.registerGlobalHelper( + 'fold', + 'ubo-dump', + ( ) => true, + (cm, start) => { + const startLineNo = start.line; + const startLine = cm.getLine(startLineNo); + let endLineNo = startLineNo; + let endLine = startLine; + const match = reFoldable.exec(startLine); + if ( match === null ) { return; } + const foldCandidate = ' ' + match[0]; + const lastLineNo = cm.lastLine(); + let nextLineNo = startLineNo + 1; + while ( nextLineNo < lastLineNo ) { + const nextLine = cm.getLine(nextLineNo); + if ( nextLine.startsWith(foldCandidate) === false ) { + if ( startLineNo >= endLineNo ) { return; } + return { + from: CodeMirror.Pos(startLineNo, startLine.length), + to: CodeMirror.Pos(endLineNo, endLine.length) + }; + } + endLine = nextLine; + endLineNo = nextLineNo; + nextLineNo += 1; + } + } +); + +const cmEditor = new CodeMirror( + document.getElementById('console'), + { + autofocus: true, + foldGutter: true, + gutters: [ 'CodeMirror-linenumbers', 'CodeMirror-foldgutter' ], + lineNumbers: true, + lineWrapping: true, + mode: 'ubo-dump', + styleActiveLine: true, + undoDepth: 5, + } +); + +uBlockDashboard.patchCodeMirrorEditor(cmEditor); + +/******************************************************************************/ + +function log(text) { + cmEditor.replaceRange(text.trim() + '\n\n', { line: 0, ch: 0 }); +} + +/******************************************************************************/ + +uDom.nodeFromId('console-clear').addEventListener('click', ( ) => { + cmEditor.setValue(''); +}); + +uDom.nodeFromId('console-fold').addEventListener('click', ( ) => { + const unfolded = []; + let maxUnfolded = -1; + cmEditor.eachLine(handle => { + const match = reFoldable.exec(handle.text); + if ( match === null ) { return; } + const depth = match[0].length; + const line = handle.lineNo(); + const isFolded = cmEditor.isFolded({ line, ch: handle.text.length }); + if ( isFolded === true ) { return; } + unfolded.push({ line, depth }); + maxUnfolded = Math.max(maxUnfolded, depth); + }); + if ( maxUnfolded === -1 ) { return; } + cmEditor.startOperation(); + for ( const details of unfolded ) { + if ( details.depth !== maxUnfolded ) { continue; } + cmEditor.foldCode(details.line, null, 'fold'); + } + cmEditor.endOperation(); +}); + +uDom.nodeFromId('console-unfold').addEventListener('click', ( ) => { + const folded = []; + let minFolded = Number.MAX_SAFE_INTEGER; + cmEditor.eachLine(handle => { + const match = reFoldable.exec(handle.text); + if ( match === null ) { return; } + const depth = match[0].length; + const line = handle.lineNo(); + const isFolded = cmEditor.isFolded({ line, ch: handle.text.length }); + if ( isFolded !== true ) { return; } + folded.push({ line, depth }); + minFolded = Math.min(minFolded, depth); + }); + if ( minFolded === Number.MAX_SAFE_INTEGER ) { return; } + cmEditor.startOperation(); + for ( const details of folded ) { + if ( details.depth !== minFolded ) { continue; } + cmEditor.foldCode(details.line, null, 'unfold'); + } + cmEditor.endOperation(); +}); + +uDom.nodeFromId('snfe-dump').addEventListener('click', ev => { + const button = ev.target; + button.setAttribute('disabled', ''); + vAPI.messaging.send('dashboard', { + what: 'snfeDump', + }).then(result => { + log(result); + button.removeAttribute('disabled'); + }); +}); + +vAPI.messaging.send('dashboard', { + what: 'getAppData', +}).then(appData => { + if ( appData.canBenchmark !== true ) { return; } + uDom.nodeFromId('snfe-benchmark').removeAttribute('disabled'); + uDom.nodeFromId('snfe-benchmark').addEventListener('click', ev => { + const button = ev.target; + button.setAttribute('disabled', ''); + vAPI.messaging.send('dashboard', { + what: 'snfeBenchmark', + }).then(result => { + log(result); + button.removeAttribute('disabled'); + }); + }); +}); + +uDom.nodeFromId('cfe-dump').addEventListener('click', ev => { + const button = ev.target; + button.setAttribute('disabled', ''); + vAPI.messaging.send('dashboard', { + what: 'cfeDump', + }).then(result => { + log(result); + button.removeAttribute('disabled'); + }); +}); + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/document-blocked.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/document-blocked.js new file mode 100644 index 0000000..e2eb034 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/document-blocked.js @@ -0,0 +1,256 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-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 +*/ + +/* global uDom */ + +'use strict'; + +/******************************************************************************/ + +(( ) => { + +/******************************************************************************/ + +const messaging = vAPI.messaging; +let details = {}; + +{ + const matches = /details=([^&]+)/.exec(window.location.search); + if ( matches !== null ) { + details = JSON.parse(decodeURIComponent(matches[1])); + } +} + +/******************************************************************************/ + +(async ( ) => { + const response = await messaging.send('documentBlocked', { + what: 'listsFromNetFilter', + rawFilter: details.fs, + }); + if ( response instanceof Object === false ) { return; } + + let lists; + for ( const rawFilter in response ) { + if ( response.hasOwnProperty(rawFilter) ) { + lists = response[rawFilter]; + break; + } + } + + if ( Array.isArray(lists) === false || lists.length === 0 ) { return; } + + const parent = uDom.nodeFromSelector('#whyex > span:nth-of-type(2)'); + for ( const list of lists ) { + const listElem = document.querySelector('#templates .filterList') + .cloneNode(true); + const sourceElem = listElem.querySelector('.filterListSource'); + sourceElem.href += encodeURIComponent(list.assetKey); + sourceElem.textContent = list.title; + if ( typeof list.supportURL === 'string' && list.supportURL !== '' ) { + const supportElem = listElem.querySelector('.filterListSupport'); + supportElem.setAttribute('href', list.supportURL); + supportElem.classList.remove('hidden'); + } + parent.appendChild(listElem); + } + uDom.nodeFromId('whyex').style.removeProperty('display'); +})(); + +/******************************************************************************/ + +(( ) => { + const matches = /^(.*)\{\{hostname\}\}(.*)$/.exec(vAPI.i18n('docblockedProceed')); + if ( matches === null ) { return; } + const proceed = document.querySelector('#templates .proceed').cloneNode(true); + proceed.children[0].textContent = matches[1]; + proceed.children[2].textContent = matches[2]; + const hnOption = proceed.querySelector('.hn'); + if ( details.hn !== details.dn ) { + hnOption.textContent = details.hn; + hnOption.setAttribute('value', details.hn); + } else { + hnOption.remove(); + } + const dnOption = proceed.querySelector('.dn'); + dnOption.textContent = details.dn; + dnOption.setAttribute('value', details.dn); + document.getElementById('proceed').append(proceed); +})(); + +/******************************************************************************/ + +uDom.nodeFromSelector('#theURL > p > span:first-of-type').textContent = details.url; +uDom.nodeFromId('why').textContent = details.fs; + +/******************************************************************************/ + +// https://github.com/gorhill/uBlock/issues/691 +// Parse URL to extract as much useful information as possible. This is +// useful to assist the user in deciding whether to navigate to the web page. +(( ) => { + if ( typeof URL !== 'function' ) { return; } + + const reURL = /^https?:\/\//; + + const liFromParam = function(name, value) { + if ( value === '' ) { + value = name; + name = ''; + } + const li = document.createElement('li'); + let span = document.createElement('span'); + span.textContent = name; + li.appendChild(span); + if ( name !== '' && value !== '' ) { + li.appendChild(document.createTextNode(' = ')); + } + span = document.createElement('span'); + if ( reURL.test(value) ) { + const a = document.createElement('a'); + a.href = a.textContent = value; + span.appendChild(a); + } else { + span.textContent = value; + } + li.appendChild(span); + return li; + }; + + // https://github.com/uBlockOrigin/uBlock-issues/issues/1649 + // Limit recursion. + const renderParams = function(parentNode, rawURL, depth = 0) { + let url; + try { + url = new URL(rawURL); + } catch(ex) { + return false; + } + + const search = url.search.slice(1); + if ( search === '' ) { return false; } + + url.search = ''; + const li = liFromParam(vAPI.i18n('docblockedNoParamsPrompt'), url.href); + parentNode.appendChild(li); + + const params = new self.URLSearchParams(search); + for ( const [ name, value ] of params ) { + const li = liFromParam(name, value); + if ( depth < 2 && reURL.test(value) ) { + const ul = document.createElement('ul'); + renderParams(ul, value, depth + 1); + li.appendChild(ul); + } + parentNode.appendChild(li); + } + + return true; + }; + + if ( renderParams(uDom.nodeFromId('parsed'), details.url) === false ) { + return; + } + + const toggler = document.querySelector('#toggleParse'); + toggler.classList.remove('hidden'); + + toggler.addEventListener('click', ( ) => { + const cl = uDom.nodeFromId('theURL').classList; + cl.toggle('collapsed'); + vAPI.localStorage.setItem( + 'document-blocked-expand-url', + (cl.contains('collapsed') === false).toString() + ); + }); + + vAPI.localStorage.getItemAsync('document-blocked-expand-url').then(value => { + uDom.nodeFromId('theURL').classList.toggle( + 'collapsed', + value !== 'true' && value !== true + ); + }); +})(); + +/******************************************************************************/ + +// https://www.reddit.com/r/uBlockOrigin/comments/breeux/close_this_window_doesnt_work_on_firefox/ + +if ( window.history.length > 1 ) { + uDom('#back').on( + 'click', + ( ) => { + window.history.back(); + } + ); + uDom('#bye').css('display', 'none'); +} else { + uDom('#bye').on( + 'click', + ( ) => { + messaging.send('documentBlocked', { + what: 'closeThisTab', + }); + } + ); + uDom('#back').css('display', 'none'); +} + +/******************************************************************************/ + +const getTargetHostname = function() { + const elem = document.querySelector('#proceed select'); + if ( elem === null ) { return details.hn; } + return elem.value; +}; + +const proceedToURL = function() { + window.location.replace(details.url); +}; + +const proceedTemporary = async function() { + await messaging.send('documentBlocked', { + what: 'temporarilyWhitelistDocument', + hostname: getTargetHostname(), + }); + proceedToURL(); +}; + +const proceedPermanent = async function() { + await messaging.send('documentBlocked', { + what: 'toggleHostnameSwitch', + name: 'no-strict-blocking', + hostname: getTargetHostname(), + deep: true, + state: true, + persist: true, + }); + proceedToURL(); +}; + +uDom('#proceedTemporary').attr('href', details.url).on('click', proceedTemporary); +uDom('#proceedPermanent').attr('href', details.url).on('click', proceedPermanent); + +/******************************************************************************/ + +})(); + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/dyna-rules.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/dyna-rules.js new file mode 100644 index 0000000..5d042b2 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/dyna-rules.js @@ -0,0 +1,680 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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/uMatrix +*/ + +/* global CodeMirror, diff_match_patch, uDom, uBlockDashboard */ + +'use strict'; + +/******************************************************************************/ + +import publicSuffixList from '../lib/publicsuffixlist/publicsuffixlist.js'; + +import { hostnameFromURI } from './uri-utils.js'; + +import './codemirror/ubo-dynamic-filtering.js'; + +/******************************************************************************/ + +const hostnameToDomainMap = new Map(); + +const mergeView = new CodeMirror.MergeView( + document.querySelector('.codeMirrorMergeContainer'), + { + allowEditingOriginals: true, + connect: 'align', + inputStyle: 'contenteditable', + lineNumbers: true, + lineWrapping: false, + origLeft: '', + revertButtons: true, + value: '', + } +); +mergeView.editor().setOption('styleActiveLine', true); +mergeView.editor().setOption('lineNumbers', false); +mergeView.leftOriginal().setOption('readOnly', 'nocursor'); + +uBlockDashboard.patchCodeMirrorEditor(mergeView.editor()); + +const thePanes = { + orig: { + doc: mergeView.leftOriginal(), + original: [], + modified: [], + }, + edit: { + doc: mergeView.editor(), + original: [], + modified: [], + }, +}; + +let cleanEditToken = 0; +let cleanEditText = ''; +let isCollapsed = false; + +/******************************************************************************/ + +// The following code is to take care of properly internationalizing +// the tooltips of the arrows used by the CodeMirror merge view. These +// are hard-coded by CodeMirror ("Push to left", "Push to right"). An +// observer is necessary because there is no hook for uBO to overwrite +// reliably the default title attribute assigned by CodeMirror. + +{ + const i18nCommitStr = vAPI.i18n('rulesCommit'); + const i18nRevertStr = vAPI.i18n('rulesRevert'); + const commitArrowSelector = '.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy-reverse:not([title="' + i18nCommitStr + '"])'; + const revertArrowSelector = '.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy:not([title="' + i18nRevertStr + '"])'; + + uDom.nodeFromSelector('.CodeMirror-merge-scrolllock') + .setAttribute('title', vAPI.i18n('genericMergeViewScrollLock')); + + const translate = function() { + let elems = document.querySelectorAll(commitArrowSelector); + for ( const elem of elems ) { + elem.setAttribute('title', i18nCommitStr); + } + elems = document.querySelectorAll(revertArrowSelector); + for ( const elem of elems ) { + elem.setAttribute('title', i18nRevertStr); + } + }; + + const mergeGapObserver = new MutationObserver(translate); + + mergeGapObserver.observe( + uDom.nodeFromSelector('.CodeMirror-merge-copybuttons-left'), + { attributes: true, attributeFilter: [ 'title' ], subtree: true } + ); + +} + +/******************************************************************************/ + +const getDiffer = (( ) => { + let differ; + return ( ) => { + if ( differ === undefined ) { differ = new diff_match_patch(); } + return differ; + }; +})(); + +/******************************************************************************/ + +// Borrowed from... +// https://github.com/codemirror/CodeMirror/blob/3e1bb5fff682f8f6cbfaef0e56c61d62403d4798/addon/search/search.js#L22 +// ... and modified as needed. + +const updateOverlay = (( ) => { + let reFilter; + const mode = { + token: function(stream) { + if ( reFilter !== undefined ) { + reFilter.lastIndex = stream.pos; + let match = reFilter.exec(stream.string); + if ( match !== null ) { + if ( match.index === stream.pos ) { + stream.pos += match[0].length || 1; + return 'searching'; + } + stream.pos = match.index; + return; + } + } + stream.skipToEnd(); + } + }; + return function(filter) { + reFilter = typeof filter === 'string' && filter !== '' ? + new RegExp(filter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi') : + undefined; + return mode; + }; +})(); + +/******************************************************************************/ + +// Incrementally update text in a CodeMirror editor for best user experience: +// - Scroll position preserved +// - Minimum amount of text updated + +const rulesToDoc = function(clearHistory) { + const orig = thePanes.orig.doc; + const edit = thePanes.edit.doc; + orig.startOperation(); + edit.startOperation(); + + for ( const key in thePanes ) { + if ( thePanes.hasOwnProperty(key) === false ) { continue; } + const doc = thePanes[key].doc; + const rules = filterRules(key); + if ( + clearHistory || + doc.lineCount() === 1 && doc.getValue() === '' || + rules.length === 0 + ) { + doc.setValue(rules.length !== 0 ? rules.join('\n') + '\n' : ''); + continue; + } + // https://github.com/uBlockOrigin/uBlock-issues/issues/593 + // Ensure the text content always ends with an empty line to avoid + // spurious diff entries. + // https://github.com/uBlockOrigin/uBlock-issues/issues/657 + // Diff against unmodified beforeText so that the last newline can + // be reported in the diff and thus appended if needed. + let beforeText = doc.getValue(); + let afterText = rules.join('\n').trim(); + if ( afterText !== '' ) { afterText += '\n'; } + const diffs = getDiffer().diff_main(beforeText, afterText); + let i = diffs.length; + let iedit = beforeText.length; + while ( i-- ) { + const diff = diffs[i]; + if ( diff[0] === 0 ) { + iedit -= diff[1].length; + continue; + } + const end = doc.posFromIndex(iedit); + if ( diff[0] === 1 ) { + doc.replaceRange(diff[1], end, end); + continue; + } + /* diff[0] === -1 */ + iedit -= diff[1].length; + const beg = doc.posFromIndex(iedit); + doc.replaceRange('', beg, end); + } + } + + // Mark ellipses as read-only + const marks = edit.getAllMarks(); + for ( const mark of marks ) { + if ( mark.uboEllipsis !== true ) { continue; } + mark.clear(); + } + if ( isCollapsed ) { + for ( let iline = 0, n = edit.lineCount(); iline < n; iline++ ) { + if ( edit.getLine(iline) !== '...' ) { continue; } + const mark = edit.markText( + { line: iline, ch: 0 }, + { line: iline + 1, ch: 0 }, + { atomic: true, readOnly: true } + ); + mark.uboEllipsis = true; + } + } + + orig.endOperation(); + edit.endOperation(); + cleanEditText = mergeView.editor().getValue().trim(); + cleanEditToken = mergeView.editor().changeGeneration(); + + if ( clearHistory !== true ) { return; } + + mergeView.editor().clearHistory(); + const chunks = mergeView.leftChunks(); + if ( chunks.length === 0 ) { return; } + const ldoc = thePanes.orig.doc; + const { clientHeight } = ldoc.getScrollInfo(); + const line = Math.min(chunks[0].editFrom, chunks[0].origFrom); + ldoc.setCursor(line, 0); + ldoc.scrollIntoView( + { line, ch: 0 }, + (clientHeight - ldoc.defaultTextHeight()) / 2 + ); +}; + +/******************************************************************************/ + +const filterRules = function(key) { + const filter = uDom.nodeFromSelector('#ruleFilter input').value; + const rules = thePanes[key].modified; + if ( filter === '' ) { return rules; } + const out = []; + for ( const rule of rules ) { + if ( rule.indexOf(filter) === -1 ) { continue; } + out.push(rule); + } + return out; +}; + +/******************************************************************************/ + +const applyDiff = async function(permanent, toAdd, toRemove) { + const details = await vAPI.messaging.send('dashboard', { + what: 'modifyRuleset', + permanent: permanent, + toAdd: toAdd, + toRemove: toRemove, + }); + thePanes.orig.original = details.permanentRules; + thePanes.edit.original = details.sessionRules; + onPresentationChanged(); +}; + +/******************************************************************************/ + +// CodeMirror quirk: sometimes fromStart.ch and/or toStart.ch is undefined. +// When this happens, use 0. + +mergeView.options.revertChunk = function( + mv, + from, fromStart, fromEnd, + to, toStart, toEnd +) { + // https://github.com/gorhill/uBlock/issues/3611 + if ( document.body.getAttribute('dir') === 'rtl' ) { + let tmp = from; from = to; to = tmp; + tmp = fromStart; fromStart = toStart; toStart = tmp; + tmp = fromEnd; fromEnd = toEnd; toEnd = tmp; + } + if ( typeof fromStart.ch !== 'number' ) { fromStart.ch = 0; } + if ( fromEnd.ch !== 0 ) { fromEnd.line += 1; } + const toAdd = from.getRange( + { line: fromStart.line, ch: 0 }, + { line: fromEnd.line, ch: 0 } + ); + if ( typeof toStart.ch !== 'number' ) { toStart.ch = 0; } + if ( toEnd.ch !== 0 ) { toEnd.line += 1; } + const toRemove = to.getRange( + { line: toStart.line, ch: 0 }, + { line: toEnd.line, ch: 0 } + ); + applyDiff(from === mv.editor(), toAdd, toRemove); +}; + +/******************************************************************************/ + +function handleImportFilePicker() { + const fileReaderOnLoadHandler = function() { + if ( typeof this.result !== 'string' || this.result === '' ) { return; } + // https://github.com/chrisaljoudi/uBlock/issues/757 + // Support RequestPolicy rule syntax + let result = this.result; + let matches = /\[origins-to-destinations\]([^\[]+)/.exec(result); + if ( matches && matches.length === 2 ) { + result = matches[1].trim() + .replace(/\|/g, ' ') + .replace(/\n/g, ' * noop\n'); + } + applyDiff(false, result, ''); + }; + const file = this.files[0]; + if ( file === undefined || file.name === '' ) { return; } + if ( file.type.indexOf('text') !== 0 ) { return; } + const fr = new FileReader(); + fr.onload = fileReaderOnLoadHandler; + fr.readAsText(file); +} + +/******************************************************************************/ + +const startImportFilePicker = function() { + const input = document.getElementById('importFilePicker'); + // Reset to empty string, this will ensure an change event is properly + // triggered if the user pick a file, even if it is the same as the last + // one picked. + input.value = ''; + input.click(); +}; + +/******************************************************************************/ + +function exportUserRulesToFile() { + const filename = vAPI.i18n('rulesDefaultFileName') + .replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString()) + .replace(/ +/g, '_'); + vAPI.download({ + url: 'data:text/plain,' + encodeURIComponent( + mergeView.leftOriginal().getValue().trim() + '\n' + ), + filename: filename, + saveAs: true + }); +} + +/******************************************************************************/ + +const onFilterChanged = (( ) => { + let timer; + let overlay = null; + let last = ''; + + const process = function() { + timer = undefined; + if ( mergeView.editor().isClean(cleanEditToken) === false ) { return; } + const filter = uDom.nodeFromSelector('#ruleFilter input').value; + if ( filter === last ) { return; } + last = filter; + if ( overlay !== null ) { + mergeView.leftOriginal().removeOverlay(overlay); + mergeView.editor().removeOverlay(overlay); + overlay = null; + } + if ( filter !== '' ) { + overlay = updateOverlay(filter); + mergeView.leftOriginal().addOverlay(overlay); + mergeView.editor().addOverlay(overlay); + } + rulesToDoc(true); + }; + + return function() { + if ( timer !== undefined ) { self.cancelIdleCallback(timer); } + timer = self.requestIdleCallback(process, { timeout: 773 }); + }; +})(); + +/******************************************************************************/ + +const onPresentationChanged = (( ) => { + let sortType = 1; + + const reSwRule = /^([^/]+): ([^/ ]+) ([^ ]+)/; + const reRule = /^([^ ]+) ([^/ ]+) ([^ ]+ [^ ]+)/; + const reUrlRule = /^([^ ]+) ([^ ]+) ([^ ]+ [^ ]+)/; + + const sortNormalizeHn = function(hn) { + let domain = hostnameToDomainMap.get(hn); + if ( domain === undefined ) { + domain = /(\d|\])$/.test(hn) + ? hn + : publicSuffixList.getDomain(hn); + hostnameToDomainMap.set(hn, domain); + } + let normalized = domain || hn; + if ( hn.length !== domain.length ) { + const subdomains = hn.slice(0, hn.length - domain.length - 1); + normalized += '.' + ( + subdomains.includes('.') + ? subdomains.split('.').reverse().join('.') + : subdomains + ); + } + return normalized; + }; + + const slotFromRule = rule => { + let type, srcHn, desHn, extra; + let match = reSwRule.exec(rule); + if ( match !== null ) { + type = ' ' + match[1]; + srcHn = sortNormalizeHn(match[2]); + desHn = srcHn; + extra = match[3]; + } else if ( (match = reRule.exec(rule)) !== null ) { + type = '\x10FFFE'; + srcHn = sortNormalizeHn(match[1]); + desHn = sortNormalizeHn(match[2]); + extra = match[3]; + } else if ( (match = reUrlRule.exec(rule)) !== null ) { + type = '\x10FFFF'; + srcHn = sortNormalizeHn(match[1]); + desHn = sortNormalizeHn(hostnameFromURI(match[2])); + extra = match[3]; + } + if ( sortType === 0 ) { + return { rule, token: `${type} ${srcHn} ${desHn} ${extra}` }; + } + if ( sortType === 1 ) { + return { rule, token: `${srcHn} ${type} ${desHn} ${extra}` }; + } + return { rule, token: `${desHn} ${type} ${srcHn} ${extra}` }; + }; + + const sort = rules => { + const slots = []; + for ( let i = 0; i < rules.length; i++ ) { + slots.push(slotFromRule(rules[i], 1)); + } + slots.sort((a, b) => a.token.localeCompare(b.token)); + for ( let i = 0; i < rules.length; i++ ) { + rules[i] = slots[i].rule; + } + }; + + const collapse = ( ) => { + if ( isCollapsed !== true ) { return; } + const diffs = getDiffer().diff_main( + thePanes.orig.modified.join('\n'), + thePanes.edit.modified.join('\n') + ); + const ll = []; let il = 0, lellipsis = false; + const rr = []; let ir = 0, rellipsis = false; + for ( let i = 0; i < diffs.length; i++ ) { + const diff = diffs[i]; + if ( diff[0] === 0 ) { + lellipsis = rellipsis = true; + il += 1; ir += 1; + continue; + } + if ( diff[0] < 0 ) { + if ( lellipsis ) { + ll.push('...'); + if ( rellipsis ) { rr.push('...'); } + lellipsis = rellipsis = false; + } + ll.push(diff[1].trim()); + il += 1; + continue; + } + /* diff[0] > 0 */ + if ( rellipsis ) { + rr.push('...'); + if ( lellipsis ) { ll.push('...'); } + lellipsis = rellipsis = false; + } + rr.push(diff[1].trim()); + ir += 1; + } + if ( lellipsis ) { ll.push('...'); } + if ( rellipsis ) { rr.push('...'); } + thePanes.orig.modified = ll; + thePanes.edit.modified = rr; + }; + + return function(clearHistory) { + const origPane = thePanes.orig; + const editPane = thePanes.edit; + origPane.modified = origPane.original.slice(); + editPane.modified = editPane.original.slice(); + const select = document.querySelector('#ruleFilter select'); + sortType = parseInt(select.value, 10); + if ( isNaN(sortType) ) { sortType = 1; } + { + const mode = origPane.doc.getMode(); + mode.sortType = sortType; + mode.setHostnameToDomainMap(hostnameToDomainMap); + mode.setPSL(publicSuffixList); + } + { + const mode = editPane.doc.getMode(); + mode.sortType = sortType; + mode.setHostnameToDomainMap(hostnameToDomainMap); + mode.setPSL(publicSuffixList); + } + sort(origPane.modified); + sort(editPane.modified); + collapse(); + rulesToDoc(clearHistory); + onTextChanged(clearHistory); + }; +})(); + +/******************************************************************************/ + +const onTextChanged = (( ) => { + let timer; + + const process = details => { + timer = undefined; + const diff = document.getElementById('diff'); + let isClean = mergeView.editor().isClean(cleanEditToken); + if ( + details === undefined && + isClean === false && + mergeView.editor().getValue().trim() === cleanEditText + ) { + cleanEditToken = mergeView.editor().changeGeneration(); + isClean = true; + } + const isDirty = mergeView.leftChunks().length !== 0; + document.body.classList.toggle('editing', isClean === false); + diff.classList.toggle('dirty', isDirty); + uDom('#editSaveButton') + .toggleClass('disabled', isClean); + uDom('#exportButton,#importButton') + .toggleClass('disabled', isClean === false); + uDom('#revertButton,#commitButton') + .toggleClass('disabled', isClean === false || isDirty === false); + const input = document.querySelector('#ruleFilter input'); + if ( isClean ) { + input.removeAttribute('disabled'); + CodeMirror.commands.save = undefined; + } else { + input.setAttribute('disabled', ''); + CodeMirror.commands.save = editSaveHandler; + } + }; + + return function(now) { + if ( timer !== undefined ) { self.cancelIdleCallback(timer); } + timer = now ? process() : self.requestIdleCallback(process, { timeout: 57 }); + }; +})(); + +/******************************************************************************/ + +const revertAllHandler = function() { + const toAdd = [], toRemove = []; + const left = mergeView.leftOriginal(); + const edit = mergeView.editor(); + for ( const chunk of mergeView.leftChunks() ) { + const addedLines = left.getRange( + { line: chunk.origFrom, ch: 0 }, + { line: chunk.origTo, ch: 0 } + ); + const removedLines = edit.getRange( + { line: chunk.editFrom, ch: 0 }, + { line: chunk.editTo, ch: 0 } + ); + toAdd.push(addedLines.trim()); + toRemove.push(removedLines.trim()); + } + applyDiff(false, toAdd.join('\n'), toRemove.join('\n')); +}; + +/******************************************************************************/ + +const commitAllHandler = function() { + const toAdd = [], toRemove = []; + const left = mergeView.leftOriginal(); + const edit = mergeView.editor(); + for ( const chunk of mergeView.leftChunks() ) { + const addedLines = edit.getRange( + { line: chunk.editFrom, ch: 0 }, + { line: chunk.editTo, ch: 0 } + ); + const removedLines = left.getRange( + { line: chunk.origFrom, ch: 0 }, + { line: chunk.origTo, ch: 0 } + ); + toAdd.push(addedLines.trim()); + toRemove.push(removedLines.trim()); + } + applyDiff(true, toAdd.join('\n'), toRemove.join('\n')); +}; + +/******************************************************************************/ + +const editSaveHandler = function() { + const editor = mergeView.editor(); + const editText = editor.getValue().trim(); + if ( editText === cleanEditText ) { + onTextChanged(true); + return; + } + const toAdd = [], toRemove = []; + const diffs = getDiffer().diff_main(cleanEditText, editText); + for ( const diff of diffs ) { + if ( diff[0] === 1 ) { + toAdd.push(diff[1]); + } else if ( diff[0] === -1 ) { + toRemove.push(diff[1]); + } + } + applyDiff(false, toAdd.join(''), toRemove.join('')); +}; + +/******************************************************************************/ + +self.cloud.onPush = function() { + return thePanes.orig.original.join('\n'); +}; + +self.cloud.onPull = function(data, append) { + if ( typeof data !== 'string' ) { return; } + applyDiff( + false, + data, + append ? '' : mergeView.editor().getValue().trim() + ); +}; + +/******************************************************************************/ + +self.hasUnsavedData = function() { + return mergeView.editor().isClean(cleanEditToken) === false; +}; + +/******************************************************************************/ + +vAPI.messaging.send('dashboard', { + what: 'getRules', +}).then(details => { + thePanes.orig.original = details.permanentRules; + thePanes.edit.original = details.sessionRules; + publicSuffixList.fromSelfie(details.pslSelfie); + onPresentationChanged(true); +}); + +// Handle user interaction +uDom('#importButton').on('click', startImportFilePicker); +uDom('#importFilePicker').on('change', handleImportFilePicker); +uDom('#exportButton').on('click', exportUserRulesToFile); +uDom('#revertButton').on('click', revertAllHandler); +uDom('#commitButton').on('click', commitAllHandler); +uDom('#editSaveButton').on('click', editSaveHandler); +uDom('#ruleFilter input').on('input', onFilterChanged); +uDom('#ruleFilter select').on('input', ( ) => { + onPresentationChanged(true); +}); +uDom('#ruleFilter #diffCollapse').on('click', ev => { + isCollapsed = ev.target.classList.toggle('active'); + onPresentationChanged(true); +}); + +// https://groups.google.com/forum/#!topic/codemirror/UQkTrt078Vs +mergeView.editor().on('updateDiff', ( ) => { onTextChanged(); }); + +/******************************************************************************/ + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/dynamic-net-filtering.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/dynamic-net-filtering.js new file mode 100644 index 0000000..28d8b51 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/dynamic-net-filtering.js @@ -0,0 +1,484 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2014-2018 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 +*/ + +'use strict'; + +/******************************************************************************/ + +import punycode from '../lib/punycode.js'; + +import { LineIterator } from './text-utils.js'; + +import { + decomposeHostname, + domainFromHostname, +} from './uri-utils.js'; + +/******************************************************************************/ + +// Object.create(null) is used below to eliminate worries about unexpected +// property names in prototype chain -- and this way we don't have to use +// hasOwnProperty() to avoid this. + +const supportedDynamicTypes = Object.create(null); +Object.assign(supportedDynamicTypes, { + '3p': true, + 'image': true, +'inline-script': true, + '1p-script': true, + '3p-script': true, + '3p-frame': true +}); + +const typeBitOffsets = Object.create(null); +Object.assign(typeBitOffsets, { + '*': 0, +'inline-script': 2, + '1p-script': 4, + '3p-script': 6, + '3p-frame': 8, + 'image': 10, + '3p': 12 +}); + +const nameToActionMap = Object.create(null); +Object.assign(nameToActionMap, { + 'block': 1, + 'allow': 2, + 'noop': 3 +}); + +const intToActionMap = new Map([ + [ 1, 'block' ], + [ 2, 'allow' ], + [ 3, 'noop' ] +]); + +// For performance purpose, as simple tests as possible +const reBadHostname = /[^0-9a-z_.\[\]:%-]/; +const reNotASCII = /[^\x20-\x7F]/; +const decomposedSource = []; +const decomposedDestination = []; + +/******************************************************************************/ + +function is3rdParty(srcHostname, desHostname) { + // If at least one is party-less, the relation can't be labelled + // "3rd-party" + if ( desHostname === '*' || srcHostname === '*' || srcHostname === '' ) { + return false; + } + + // No domain can very well occurs, for examples: + // - localhost + // - file-scheme + // etc. + const srcDomain = domainFromHostname(srcHostname) || srcHostname; + + if ( desHostname.endsWith(srcDomain) === false ) { + return true; + } + // Do not confuse 'example.com' with 'anotherexample.com' + return desHostname.length !== srcDomain.length && + desHostname.charAt(desHostname.length - srcDomain.length - 1) !== '.'; +} + +/******************************************************************************/ + +class DynamicHostRuleFiltering { + + constructor() { + this.reset(); + } + + reset() { + this.r = 0; + this.type = ''; + this.y = ''; + this.z = ''; + this.rules = new Map(); + this.changed = false; + } + + assign(other) { + // Remove rules not in other + for ( const k of this.rules.keys() ) { + if ( other.rules.has(k) === false ) { + this.rules.delete(k); + this.changed = true; + } + } + // Add/change rules in other + for ( const entry of other.rules ) { + if ( this.rules.get(entry[0]) !== entry[1] ) { + this.rules.set(entry[0], entry[1]); + this.changed = true; + } + } + } + + copyRules(from, srcHostname, desHostnames) { + // Specific types + let thisBits = this.rules.get('* *'); + let fromBits = from.rules.get('* *'); + if ( fromBits !== thisBits ) { + if ( fromBits !== undefined ) { + this.rules.set('* *', fromBits); + } else { + this.rules.delete('* *'); + } + this.changed = true; + } + + let key = `${srcHostname} *`; + thisBits = this.rules.get(key); + fromBits = from.rules.get(key); + if ( fromBits !== thisBits ) { + if ( fromBits !== undefined ) { + this.rules.set(key, fromBits); + } else { + this.rules.delete(key); + } + this.changed = true; + } + + // Specific destinations + for ( const desHostname in desHostnames ) { + key = `* ${desHostname}`; + thisBits = this.rules.get(key); + fromBits = from.rules.get(key); + if ( fromBits !== thisBits ) { + if ( fromBits !== undefined ) { + this.rules.set(key, fromBits); + } else { + this.rules.delete(key); + } + this.changed = true; + } + key = `${srcHostname} ${desHostname}` ; + thisBits = this.rules.get(key); + fromBits = from.rules.get(key); + if ( fromBits !== thisBits ) { + if ( fromBits !== undefined ) { + this.rules.set(key, fromBits); + } else { + this.rules.delete(key); + } + this.changed = true; + } + } + + return this.changed; + } + + // - * * type + // - from * type + // - * to * + // - from to * + + hasSameRules(other, srcHostname, desHostnames) { + // Specific types + let key = '* *'; + if ( this.rules.get(key) !== other.rules.get(key) ) { return false; } + key = `${srcHostname} *`; + if ( this.rules.get(key) !== other.rules.get(key) ) { return false; } + // Specific destinations + for ( const desHostname in desHostnames ) { + key = `* ${desHostname}`; + if ( this.rules.get(key) !== other.rules.get(key) ) { + return false; + } + key = `${srcHostname} ${desHostname}`; + if ( this.rules.get(key) !== other.rules.get(key) ) { + return false; + } + } + return true; + } + + setCell(srcHostname, desHostname, type, state) { + const bitOffset = typeBitOffsets[type]; + const k = `${srcHostname} ${desHostname}`; + const oldBitmap = this.rules.get(k) || 0; + const newBitmap = oldBitmap & ~(3 << bitOffset) | (state << bitOffset); + if ( newBitmap === oldBitmap ) { return false; } + if ( newBitmap === 0 ) { + this.rules.delete(k); + } else { + this.rules.set(k, newBitmap); + } + this.changed = true; + return true; + } + + unsetCell(srcHostname, desHostname, type) { + this.evaluateCellZY(srcHostname, desHostname, type); + if ( this.r === 0 ) { return false; } + this.setCell(srcHostname, desHostname, type, 0); + this.changed = true; + return true; + } + + evaluateCell(srcHostname, desHostname, type) { + const key = `${srcHostname} ${desHostname}`; + const bitmap = this.rules.get(key); + if ( bitmap === undefined ) { return 0; } + return bitmap >> typeBitOffsets[type] & 3; + } + + clearRegisters() { + this.r = 0; + this.type = this.y = this.z = ''; + return this; + } + + evaluateCellZ(srcHostname, desHostname, type) { + decomposeHostname(srcHostname, decomposedSource); + this.type = type; + const bitOffset = typeBitOffsets[type]; + for ( const srchn of decomposedSource ) { + this.z = srchn; + let v = this.rules.get(`${srchn} ${desHostname}`); + if ( v === undefined ) { continue; } + v = v >>> bitOffset & 3; + if ( v === 0 ) { continue; } + return (this.r = v); + } + // srcHostname is '*' at this point + this.r = 0; + return 0; + } + + evaluateCellZY(srcHostname, desHostname, type) { + // Pathological cases. + if ( desHostname === '' ) { + this.r = 0; + return 0; + } + + // Precedence: from most specific to least specific + + // Specific-destination, any party, any type + decomposeHostname(desHostname, decomposedDestination); + for ( const deshn of decomposedDestination ) { + if ( deshn === '*' ) { break; } + this.y = deshn; + if ( this.evaluateCellZ(srcHostname, deshn, '*') !== 0 ) { + return this.r; + } + } + + const thirdParty = is3rdParty(srcHostname, desHostname); + + // Any destination + this.y = '*'; + + // Specific party + // TODO: equate `object` as `sub_frame` + if ( thirdParty ) { + // 3rd-party, specific type + if ( type === 'script' ) { + if ( this.evaluateCellZ(srcHostname, '*', '3p-script') !== 0 ) { + return this.r; + } + } else if ( type === 'sub_frame' ) { + if ( this.evaluateCellZ(srcHostname, '*', '3p-frame') !== 0 ) { + return this.r; + } + } + // 3rd-party, any type + if ( this.evaluateCellZ(srcHostname, '*', '3p') !== 0 ) { + return this.r; + } + } else if ( type === 'script' ) { + // 1st party, specific type + if ( this.evaluateCellZ(srcHostname, '*', '1p-script') !== 0 ) { + return this.r; + } + } + + // Any destination, any party, specific type + if ( supportedDynamicTypes[type] !== undefined ) { + if ( this.evaluateCellZ(srcHostname, '*', type) !== 0 ) { + return this.r; + } + } + + // Any destination, any party, any type + if ( this.evaluateCellZ(srcHostname, '*', '*') !== 0 ) { + return this.r; + } + + this.type = ''; + return 0; + } + + mustAllowCellZY(srcHostname, desHostname, type) { + return this.evaluateCellZY(srcHostname, desHostname, type) === 2; + } + + mustBlockOrAllow() { + return this.r === 1 || this.r === 2; + } + + mustBlock() { + return this.r === 1; + } + + mustAbort() { + return this.r === 3; + } + + lookupRuleData(src, des, type) { + const r = this.evaluateCellZY(src, des, type); + if ( r === 0 ) { return; } + return `${this.z} ${this.y} ${this.type} ${r}`; + } + + toLogData() { + if ( this.r === 0 || this.type === '' ) { return; } + return { + source: 'dynamicHost', + result: this.r, + raw: `${this.z} ${this.y} ${this.type} ${intToActionMap.get(this.r)}` + }; + } + + srcHostnameFromRule(rule) { + return rule.slice(0, rule.indexOf(' ')); + } + + desHostnameFromRule(rule) { + return rule.slice(rule.indexOf(' ') + 1); + } + + toArray() { + const out = []; + for ( const key of this.rules.keys() ) { + const srchn = this.srcHostnameFromRule(key); + const deshn = this.desHostnameFromRule(key); + const srchnPretty = srchn.includes('xn--') && punycode + ? punycode.toUnicode(srchn) + : srchn; + const deshnPretty = deshn.includes('xn--') && punycode + ? punycode.toUnicode(deshn) + : deshn; + for ( const type in typeBitOffsets ) { + if ( typeBitOffsets[type] === undefined ) { continue; } + const val = this.evaluateCell(srchn, deshn, type); + if ( val === 0 ) { continue; } + const action = intToActionMap.get(val); + if ( action === undefined ) { continue; } + out.push(`${srchnPretty} ${deshnPretty} ${type} ${action}`); + } + } + return out; + } + + toString() { + return this.toArray().join('\n'); + } + + fromString(text, append) { + const lineIter = new LineIterator(text); + if ( append !== true ) { this.reset(); } + while ( lineIter.eot() === false ) { + this.addFromRuleParts(lineIter.next().trim().split(/\s+/)); + } + } + + validateRuleParts(parts) { + if ( parts.length < 4 ) { return; } + + // Ignore hostname-based switch rules + if ( parts[0].endsWith(':') ) { return; } + + // Ignore URL-based rules + if ( parts[1].includes('/') ) { return; } + + if ( typeBitOffsets[parts[2]] === undefined ) { return; } + + if ( nameToActionMap[parts[3]] === undefined ) { return; } + + // https://github.com/chrisaljoudi/uBlock/issues/840 + // Discard invalid rules + if ( parts[1] !== '*' && parts[2] !== '*' ) { return; } + + // Performance: avoid punycoding when only ASCII chars + if ( punycode !== undefined ) { + if ( reNotASCII.test(parts[0]) ) { + parts[0] = punycode.toASCII(parts[0]); + } + if ( reNotASCII.test(parts[1]) ) { + parts[1] = punycode.toASCII(parts[1]); + } + } + + // https://github.com/chrisaljoudi/uBlock/issues/1082 + // Discard rules with invalid hostnames + if ( + (parts[0] !== '*' && reBadHostname.test(parts[0])) || + (parts[1] !== '*' && reBadHostname.test(parts[1])) + ) { + return; + } + + return parts; + } + + addFromRuleParts(parts) { + if ( this.validateRuleParts(parts) !== undefined ) { + this.setCell(parts[0], parts[1], parts[2], nameToActionMap[parts[3]]); + return true; + } + return false; + } + + removeFromRuleParts(parts) { + if ( this.validateRuleParts(parts) !== undefined ) { + this.setCell(parts[0], parts[1], parts[2], 0); + return true; + } + return false; + } + + toSelfie() { + return { + magicId: this.magicId, + rules: Array.from(this.rules) + }; + } + + fromSelfie(selfie) { + if ( selfie.magicId !== this.magicId ) { return false; } + this.rules = new Map(selfie.rules); + this.changed = true; + return true; + } +} + +DynamicHostRuleFiltering.prototype.magicId = 1; + +/******************************************************************************/ + +export default DynamicHostRuleFiltering; + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/epicker-ui.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/epicker-ui.js new file mode 100644 index 0000000..08476ae --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/epicker-ui.js @@ -0,0 +1,926 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +/* global CodeMirror */ + +'use strict'; + +/******************************************************************************/ + +import './codemirror/ubo-static-filtering.js'; + +import { hostnameFromURI } from './uri-utils.js'; +import { StaticFilteringParser } from './static-filtering-parser.js'; + +/******************************************************************************/ +/******************************************************************************/ + +(( ) => { + +/******************************************************************************/ + +if ( typeof vAPI !== 'object' ) { return; } + +const $id = id => document.getElementById(id); +const $stor = selector => document.querySelector(selector); +const $storAll = selector => document.querySelectorAll(selector); + +const pickerRoot = document.documentElement; +const dialog = $stor('aside'); +let staticFilteringParser; + +const svgRoot = $stor('svg'); +const svgOcean = svgRoot.children[0]; +const svgIslands = svgRoot.children[1]; +const NoPaths = 'M0 0'; + +const reCosmeticAnchor = /^#[$?]?#/; + +const epickerId = (( ) => { + const url = new URL(self.location.href); + if ( url.searchParams.has('zap') ) { + pickerRoot.classList.add('zap'); + } + return url.searchParams.get('epid'); +})(); +if ( epickerId === null ) { return; } + +const docURL = new URL(vAPI.getURL('')); + +let epickerConnectionId; +let resultsetOpt; + +let netFilterCandidates = []; +let cosmeticFilterCandidates = []; +let computedCandidateSlot = 0; +let computedCandidate = ''; +const computedSpecificityCandidates = new Map(); +let needBody = false; + +/******************************************************************************/ + +const cmEditor = new CodeMirror(document.querySelector('.codeMirrorContainer'), { + autoCloseBrackets: true, + autofocus: true, + extraKeys: { + 'Ctrl-Space': 'autocomplete', + }, + lineWrapping: true, + matchBrackets: true, + maxScanLines: 1, +}); + +vAPI.messaging.send('dashboard', { + what: 'getAutoCompleteDetails' +}).then(response => { + // For unknown reasons, `instanceof Object` does not work here in Firefox. + if ( typeof response !== 'object' ) { return; } + const mode = cmEditor.getMode(); + if ( mode.setHints instanceof Function ) { + mode.setHints(response); + } +}); + +/******************************************************************************/ + +const rawFilterFromTextarea = function() { + const text = cmEditor.getValue(); + const pos = text.indexOf('\n'); + return pos === -1 ? text : text.slice(0, pos); +}; + +/******************************************************************************/ + +const filterFromTextarea = function() { + const filter = rawFilterFromTextarea(); + if ( filter === '' ) { return ''; } + const sfp = staticFilteringParser; + sfp.analyze(filter); + sfp.analyzeExtra(); + if ( + sfp.category !== sfp.CATStaticExtFilter && + sfp.category !== sfp.CATStaticNetFilter || + sfp.shouldDiscard() + ) { + return '!'; + } + return filter; +}; + +/******************************************************************************/ + +const renderRange = function(id, value, invert = false) { + const input = $stor(`#${id} input`); + const max = parseInt(input.max, 10); + if ( typeof value !== 'number' ) { + value = parseInt(input.value, 10); + } + if ( invert ) { + value = max - value; + } + input.value = value; + const slider = $stor(`#${id} > span`); + const lside = slider.children[0]; + const thumb = slider.children[1]; + const sliderWidth = slider.offsetWidth; + const maxPercent = (sliderWidth - thumb.offsetWidth) / sliderWidth * 100; + const widthPercent = value / max * maxPercent; + lside.style.width = `${widthPercent}%`; +}; + +/******************************************************************************/ + +const userFilterFromCandidate = function(filter) { + if ( filter === '' || filter === '!' ) { return; } + + const hn = hostnameFromURI(docURL.href); + + // Cosmetic filter? + if ( reCosmeticAnchor.test(filter) ) { + return hn + filter; + } + + // Assume net filter + const opts = []; + + // If no domain included in filter, we need domain option + if ( filter.startsWith('||') === false ) { + opts.push(`domain=${hn}`); + } + + if ( resultsetOpt !== undefined ) { + opts.push(resultsetOpt); + } + + if ( opts.length ) { + filter += '$' + opts.join(','); + } + + return filter; +}; + +/******************************************************************************/ + +const candidateFromFilterChoice = function(filterChoice) { + let { slot, filters } = filterChoice; + let filter = filters[slot]; + + // https://github.com/uBlockOrigin/uBlock-issues/issues/47 + for ( const elem of $storAll('#candidateFilters li') ) { + elem.classList.remove('active'); + } + + computedCandidateSlot = slot; + computedCandidate = ''; + + if ( filter === undefined ) { return ''; } + + // For net filters there no such thing as a path + if ( filter.startsWith('##') === false ) { + $stor(`#netFilters li:nth-of-type(${slot+1})`) + .classList.add('active'); + return filter; + } + + // At this point, we have a cosmetic filter + + $stor(`#cosmeticFilters li:nth-of-type(${slot+1})`) + .classList.add('active'); + + return cosmeticCandidatesFromFilterChoice(filterChoice); +}; + +/******************************************************************************/ + +const cosmeticCandidatesFromFilterChoice = function(filterChoice) { + let { slot, filters } = filterChoice; + + renderRange('resultsetDepth', slot, true); + renderRange('resultsetSpecificity'); + + if ( computedSpecificityCandidates.has(slot) ) { + onCandidatesOptimized({ slot }); + return; + } + + const specificities = [ + 0b0000, // remove hierarchy; remove id, nth-of-type, attribute values + 0b0010, // remove hierarchy; remove id, nth-of-type + 0b0011, // remove hierarchy + 0b1000, // trim hierarchy; remove id, nth-of-type, attribute values + 0b1010, // trim hierarchy; remove id, nth-of-type + 0b1100, // remove id, nth-of-type, attribute values + 0b1110, // remove id, nth-of-type + 0b1111, // keep all = most specific + ]; + + const candidates = []; + + let filter = filters[slot]; + + for ( const specificity of specificities ) { + // Return path: the target element, then all siblings prepended + const paths = []; + for ( let i = slot; i < filters.length; i++ ) { + filter = filters[i].slice(2); + // Remove id, nth-of-type + // https://github.com/uBlockOrigin/uBlock-issues/issues/162 + // Mind escaped periods: they do not denote a class identifier. + if ( (specificity & 0b0001) === 0 ) { + filter = filter.replace(/:nth-of-type\(\d+\)/, ''); + if ( + filter.charAt(0) === '#' && ( + (specificity & 0b1000) === 0 || i === slot + ) + ) { + const pos = filter.search(/[^\\]\./); + if ( pos !== -1 ) { + filter = filter.slice(pos + 1); + } + } + } + // Remove attribute values. + if ( (specificity & 0b0010) === 0 ) { + const match = /^\[([^^*$=]+)[\^*$]?=.+\]$/.exec(filter); + if ( match !== null ) { + filter = `[${match[1]}]`; + } + } + // Remove all classes when an id exists. + // https://github.com/uBlockOrigin/uBlock-issues/issues/162 + // Mind escaped periods: they do not denote a class identifier. + if ( filter.charAt(0) === '#' ) { + filter = filter.replace(/([^\\])\..+$/, '$1'); + } + if ( paths.length !== 0 ) { + filter += ' > '; + } + paths.unshift(filter); + // Stop at any element with an id: these are unique in a web page + if ( (specificity & 0b1000) === 0 || filter.startsWith('#') ) { + break; + } + } + + // Trim hierarchy: remove generic elements from path + if ( (specificity & 0b1100) === 0b1000 ) { + let i = 0; + while ( i < paths.length - 1 ) { + if ( /^[a-z0-9]+ > $/.test(paths[i+1]) ) { + if ( paths[i].endsWith(' > ') ) { + paths[i] = paths[i].slice(0, -2); + } + paths.splice(i + 1, 1); + } else { + i += 1; + } + } + } + + if ( + needBody && + paths.length !== 0 && + paths[0].startsWith('#') === false && + paths[0].startsWith('body ') === false && + (specificity & 0b1100) !== 0 + ) { + paths.unshift('body > '); + } + + candidates.push(paths); + } + + vAPI.MessagingConnection.sendTo(epickerConnectionId, { + what: 'optimizeCandidates', + candidates, + slot, + }); +}; + +/******************************************************************************/ + +const onCandidatesOptimized = function(details) { + $id('resultsetModifiers').classList.remove('hide'); + const i = parseInt($stor('#resultsetSpecificity input').value, 10); + if ( Array.isArray(details.candidates) ) { + computedSpecificityCandidates.set(details.slot, details.candidates); + } + const candidates = computedSpecificityCandidates.get(details.slot); + computedCandidate = candidates[i]; + cmEditor.setValue(computedCandidate); + cmEditor.clearHistory(); + onCandidateChanged(); +}; + +/******************************************************************************/ + +const onSvgClicked = function(ev) { + // If zap mode, highlight element under mouse, this makes the zapper usable + // on touch screens. + if ( pickerRoot.classList.contains('zap') ) { + vAPI.MessagingConnection.sendTo(epickerConnectionId, { + what: 'zapElementAtPoint', + mx: ev.clientX, + my: ev.clientY, + options: { + stay: ev.shiftKey || ev.type === 'touch', + highlight: ev.target !== svgIslands, + }, + }); + return; + } + // https://github.com/chrisaljoudi/uBlock/issues/810#issuecomment-74600694 + // Unpause picker if: + // - click outside dialog AND + // - not in preview mode + if ( pickerRoot.classList.contains('paused') ) { + if ( pickerRoot.classList.contains('preview') === false ) { + unpausePicker(); + } + return; + } + // Force dialog to always be visible when using a touch-driven device. + if ( ev.type === 'touch' ) { + pickerRoot.classList.add('show'); + } + vAPI.MessagingConnection.sendTo(epickerConnectionId, { + what: 'filterElementAtPoint', + mx: ev.clientX, + my: ev.clientY, + broad: ev.ctrlKey, + }); +}; + +/******************************************************************************* + + Swipe right: + If picker not paused: quit picker + If picker paused and dialog visible: hide dialog + If picker paused and dialog not visible: quit picker + + Swipe left: + If picker paused and dialog not visible: show dialog + +*/ + +const onSvgTouch = (( ) => { + let startX = 0, startY = 0; + let t0 = 0; + return ev => { + if ( ev.type === 'touchstart' ) { + startX = ev.touches[0].screenX; + startY = ev.touches[0].screenY; + t0 = ev.timeStamp; + return; + } + if ( startX === undefined ) { return; } + const stopX = ev.changedTouches[0].screenX; + const stopY = ev.changedTouches[0].screenY; + const angle = Math.abs(Math.atan2(stopY - startY, stopX - startX)); + const distance = Math.sqrt( + Math.pow(stopX - startX, 2), + Math.pow(stopY - startY, 2) + ); + // Interpret touch events as a tap if: + // - Swipe is not valid; and + // - The time between start and stop was less than 200ms. + const duration = ev.timeStamp - t0; + if ( distance < 32 && duration < 200 ) { + onSvgClicked({ + type: 'touch', + target: ev.target, + clientX: ev.changedTouches[0].pageX, + clientY: ev.changedTouches[0].pageY, + }); + ev.preventDefault(); + return; + } + if ( distance < 64 ) { return; } + const angleUpperBound = Math.PI * 0.25 * 0.5; + const swipeRight = angle < angleUpperBound; + if ( swipeRight === false && angle < Math.PI - angleUpperBound ) { + return; + } + if ( ev.cancelable ) { + ev.preventDefault(); + } + // Swipe left. + if ( swipeRight === false ) { + if ( pickerRoot.classList.contains('paused') ) { + pickerRoot.classList.remove('hide'); + pickerRoot.classList.add('show'); + } + return; + } + // Swipe right. + if ( + pickerRoot.classList.contains('zap') && + svgIslands.getAttribute('d') !== NoPaths + ) { + vAPI.MessagingConnection.sendTo(epickerConnectionId, { + what: 'unhighlight' + }); + return; + } + else if ( + pickerRoot.classList.contains('paused') && + pickerRoot.classList.contains('show') + ) { + pickerRoot.classList.remove('show'); + pickerRoot.classList.add('hide'); + return; + } + quitPicker(); + }; +})(); + +/******************************************************************************/ + +const onCandidateChanged = function() { + const filter = filterFromTextarea(); + const bad = filter === '!'; + $stor('section').classList.toggle('invalidFilter', bad); + if ( bad ) { + $id('resultsetCount').textContent = 'E'; + $id('create').setAttribute('disabled', ''); + } + const text = rawFilterFromTextarea(); + $id('resultsetModifiers').classList.toggle( + 'hide', text === '' || text !== computedCandidate + ); + vAPI.MessagingConnection.sendTo(epickerConnectionId, { + what: 'dialogSetFilter', + filter, + compiled: reCosmeticAnchor.test(filter) + ? staticFilteringParser.result.compiled + : undefined, + }); +}; + +/******************************************************************************/ + +const onPreviewClicked = function() { + const state = pickerRoot.classList.toggle('preview'); + vAPI.MessagingConnection.sendTo(epickerConnectionId, { + what: 'togglePreview', + state, + }); +}; + +/******************************************************************************/ + +const onCreateClicked = function() { + const candidate = filterFromTextarea(); + const filter = userFilterFromCandidate(candidate); + if ( filter !== undefined ) { + vAPI.messaging.send('elementPicker', { + what: 'createUserFilter', + autoComment: true, + filters: filter, + docURL: docURL.href, + killCache: reCosmeticAnchor.test(candidate) === false, + }); + } + vAPI.MessagingConnection.sendTo(epickerConnectionId, { + what: 'dialogCreate', + filter: candidate, + compiled: reCosmeticAnchor.test(candidate) + ? staticFilteringParser.result.compiled + : undefined, + }); +}; + +/******************************************************************************/ + +const onPickClicked = function() { + unpausePicker(); +}; + +/******************************************************************************/ + +const onQuitClicked = function() { + quitPicker(); +}; + +/******************************************************************************/ + +const onDepthChanged = function() { + const input = $stor('#resultsetDepth input'); + const max = parseInt(input.max, 10); + const value = parseInt(input.value, 10); + const text = candidateFromFilterChoice({ + filters: cosmeticFilterCandidates, + slot: max - value, + }); + if ( text === undefined ) { return; } + cmEditor.setValue(text); + cmEditor.clearHistory(); + onCandidateChanged(); +}; + +/******************************************************************************/ + +const onSpecificityChanged = function() { + renderRange('resultsetSpecificity'); + if ( rawFilterFromTextarea() !== computedCandidate ) { return; } + const depthInput = $stor('#resultsetDepth input'); + const slot = parseInt(depthInput.max, 10) - parseInt(depthInput.value, 10); + const i = parseInt($stor('#resultsetSpecificity input').value, 10); + const candidates = computedSpecificityCandidates.get(slot); + computedCandidate = candidates[i]; + cmEditor.setValue(computedCandidate); + cmEditor.clearHistory(); + onCandidateChanged(); +}; + +/******************************************************************************/ + +const onCandidateClicked = function(ev) { + let li = ev.target.closest('li'); + if ( li === null ) { return; } + const ul = li.closest('.changeFilter'); + if ( ul === null ) { return; } + const choice = { + filters: Array.from(ul.querySelectorAll('li')).map(a => a.textContent), + slot: 0, + }; + while ( li.previousElementSibling !== null ) { + li = li.previousElementSibling; + choice.slot += 1; + } + const text = candidateFromFilterChoice(choice); + if ( text === undefined ) { return; } + cmEditor.setValue(text); + cmEditor.clearHistory(); + onCandidateChanged(); +}; + +/******************************************************************************/ + +const onKeyPressed = function(ev) { + // Delete + if ( + (ev.key === 'Delete' || ev.key === 'Backspace') && + pickerRoot.classList.contains('zap') + ) { + vAPI.MessagingConnection.sendTo(epickerConnectionId, { + what: 'zapElementAtPoint', + options: { stay: true }, + }); + return; + } + // Esc + if ( ev.key === 'Escape' || ev.which === 27 ) { + onQuitClicked(); + return; + } +}; + +/******************************************************************************/ + +const onStartMoving = (( ) => { + let isTouch = false; + let mx0 = 0, my0 = 0; + let mx1 = 0, my1 = 0; + let r0 = 0, b0 = 0; + let rMax = 0, bMax = 0; + let timer; + + const move = ( ) => { + timer = undefined; + const r1 = Math.min(Math.max(r0 - mx1 + mx0, 2), rMax); + const b1 = Math.min(Math.max(b0 - my1 + my0, 2), bMax); + dialog.style.setProperty('right', `${r1}px`); + dialog.style.setProperty('bottom', `${b1}px`); + }; + + const moveAsync = ev => { + if ( timer !== undefined ) { return; } + if ( isTouch ) { + const touch = ev.touches[0]; + mx1 = touch.pageX; + my1 = touch.pageY; + } else { + mx1 = ev.pageX; + my1 = ev.pageY; + } + timer = self.requestAnimationFrame(move); + }; + + const stop = ev => { + if ( dialog.classList.contains('moving') === false ) { return; } + dialog.classList.remove('moving'); + if ( isTouch ) { + self.removeEventListener('touchmove', moveAsync, { capture: true }); + } else { + self.removeEventListener('mousemove', moveAsync, { capture: true }); + } + eatEvent(ev); + }; + + return function(ev) { + const target = dialog.querySelector('#move'); + if ( ev.target !== target ) { return; } + if ( dialog.classList.contains('moving') ) { return; } + isTouch = ev.type.startsWith('touch'); + if ( isTouch ) { + const touch = ev.touches[0]; + mx0 = touch.pageX; + my0 = touch.pageY; + } else { + mx0 = ev.pageX; + my0 = ev.pageY; + } + const style = self.getComputedStyle(dialog); + r0 = parseInt(style.right, 10); + b0 = parseInt(style.bottom, 10); + const rect = dialog.getBoundingClientRect(); + rMax = pickerRoot.clientWidth - 2 - rect.width ; + bMax = pickerRoot.clientHeight - 2 - rect.height; + dialog.classList.add('moving'); + if ( isTouch ) { + self.addEventListener('touchmove', moveAsync, { capture: true }); + self.addEventListener('touchend', stop, { capture: true, once: true }); + } else { + self.addEventListener('mousemove', moveAsync, { capture: true }); + self.addEventListener('mouseup', stop, { capture: true, once: true }); + } + eatEvent(ev); + }; +})(); + +/******************************************************************************/ + +const svgListening = (( ) => { + let on = false; + let timer; + let mx = 0, my = 0; + + const onTimer = ( ) => { + timer = undefined; + vAPI.MessagingConnection.sendTo(epickerConnectionId, { + what: 'highlightElementAtPoint', + mx, + my, + }); + }; + + const onHover = ev => { + mx = ev.clientX; + my = ev.clientY; + if ( timer === undefined ) { + timer = self.requestAnimationFrame(onTimer); + } + }; + + return state => { + if ( state === on ) { return; } + on = state; + if ( on ) { + document.addEventListener('mousemove', onHover, { passive: true }); + return; + } + document.removeEventListener('mousemove', onHover, { passive: true }); + if ( timer !== undefined ) { + self.cancelAnimationFrame(timer); + timer = undefined; + } + }; +})(); + +/******************************************************************************/ + +const eatEvent = function(ev) { + ev.stopPropagation(); + ev.preventDefault(); +}; + +/******************************************************************************/ + +// Create lists of candidate filters. This takes into account whether the +// current mode is narrow or broad. + +const populateCandidates = function(candidates, selector) { + + const root = dialog.querySelector(selector); + const ul = root.querySelector('ul'); + while ( ul.firstChild !== null ) { + ul.firstChild.remove(); + } + for ( let i = 0; i < candidates.length; i++ ) { + const li = document.createElement('li'); + li.textContent = candidates[i]; + ul.appendChild(li); + } + if ( candidates.length !== 0 ) { + root.style.removeProperty('display'); + } else { + root.style.setProperty('display', 'none'); + } +}; + +/******************************************************************************/ + +const showDialog = function(details) { + pausePicker(); + + const { netFilters, cosmeticFilters, filter } = details; + + netFilterCandidates = netFilters; + + needBody = + cosmeticFilters.length !== 0 && + cosmeticFilters[cosmeticFilters.length - 1] === '##body'; + if ( needBody ) { + cosmeticFilters.pop(); + } + cosmeticFilterCandidates = cosmeticFilters; + + docURL.href = details.url; + + populateCandidates(netFilters, '#netFilters'); + populateCandidates(cosmeticFilters, '#cosmeticFilters'); + computedSpecificityCandidates.clear(); + + const depthInput = $stor('#resultsetDepth input'); + depthInput.max = cosmeticFilters.length - 1; + depthInput.value = depthInput.max; + + dialog.querySelector('ul').style.display = + netFilters.length || cosmeticFilters.length ? '' : 'none'; + $id('create').setAttribute('disabled', ''); + + // Auto-select a candidate filter + + // 2020-09-01: + // In Firefox, `details instanceof Object` resolves to `false` despite + // `details` being a valid object. Consequently, falling back to use + // `typeof details`. + // This is an issue which surfaced when the element picker code was + // revisited to isolate the picker dialog DOM from the page DOM. + if ( typeof filter !== 'object' || filter === null ) { + cmEditor.setValue(''); + return; + } + + const filterChoice = { + filters: filter.filters, + slot: filter.slot, + }; + + const text = candidateFromFilterChoice(filterChoice); + if ( text === undefined ) { return; } + cmEditor.setValue(text); + onCandidateChanged(); +}; + +/******************************************************************************/ + +const pausePicker = function() { + pickerRoot.classList.add('paused'); + svgListening(false); +}; + +/******************************************************************************/ + +const unpausePicker = function() { + pickerRoot.classList.remove('paused', 'preview'); + vAPI.MessagingConnection.sendTo(epickerConnectionId, { + what: 'togglePreview', + state: false, + }); + svgListening(true); +}; + +/******************************************************************************/ + +const startPicker = function() { + self.addEventListener('keydown', onKeyPressed, true); + const svg = $stor('svg'); + svg.addEventListener('click', onSvgClicked); + svg.addEventListener('touchstart', onSvgTouch); + svg.addEventListener('touchend', onSvgTouch); + + unpausePicker(); + + if ( pickerRoot.classList.contains('zap') ) { return; } + + cmEditor.on('changes', onCandidateChanged); + + $id('preview').addEventListener('click', onPreviewClicked); + $id('create').addEventListener('click', onCreateClicked); + $id('pick').addEventListener('click', onPickClicked); + $id('quit').addEventListener('click', onQuitClicked); + $id('move').addEventListener('mousedown', onStartMoving); + $id('move').addEventListener('touchstart', onStartMoving); + $id('candidateFilters').addEventListener('click', onCandidateClicked); + $stor('#resultsetDepth input').addEventListener('input', onDepthChanged); + $stor('#resultsetSpecificity input').addEventListener('input', onSpecificityChanged); + staticFilteringParser = new StaticFilteringParser({ interactive: true }); +}; + +/******************************************************************************/ + +const quitPicker = function() { + vAPI.MessagingConnection.sendTo(epickerConnectionId, { what: 'quitPicker' }); + vAPI.MessagingConnection.disconnectFrom(epickerConnectionId); +}; + +/******************************************************************************/ + +const onPickerMessage = function(msg) { + switch ( msg.what ) { + case 'candidatesOptimized': + onCandidatesOptimized(msg); + break; + case 'showDialog': + showDialog(msg); + break; + case 'resultsetDetails': { + resultsetOpt = msg.opt; + $id('resultsetCount').textContent = msg.count; + if ( msg.count !== 0 ) { + $id('create').removeAttribute('disabled'); + } else { + $id('create').setAttribute('disabled', ''); + } + break; + } + case 'svgPaths': { + let { ocean, islands } = msg; + ocean += islands; + svgOcean.setAttribute('d', ocean); + svgIslands.setAttribute('d', islands || NoPaths); + break; + } + default: + break; + } +}; + +/******************************************************************************/ + +const onConnectionMessage = function(msg) { + switch ( msg.what ) { + case 'connectionBroken': + break; + case 'connectionMessage': + onPickerMessage(msg.payload); + break; + case 'connectionAccepted': + epickerConnectionId = msg.id; + startPicker(); + vAPI.MessagingConnection.sendTo(epickerConnectionId, { + what: 'start', + }); + break; + } +}; + +vAPI.MessagingConnection.connectTo( + `epickerDialog-${epickerId}`, + `epicker-${epickerId}`, + onConnectionMessage +); + +/******************************************************************************/ + +})(); + + + + + + + + +/******************************************************************************* + + DO NOT: + - Remove the following code + - Add code beyond the following code + Reason: + - https://github.com/gorhill/uBlock/pull/3721 + - uBO never uses the return value from injected content scripts + +**/ + +void 0; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/fa-icons.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/fa-icons.js new file mode 100644 index 0000000..67638ba --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/fa-icons.js @@ -0,0 +1,126 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2018-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/uMatrix +*/ + +'use strict'; + +/******************************************************************************/ + +const faIconsInit = (( ) => { + + // https://github.com/uBlockOrigin/uBlock-issues/issues/1196 + const svgIcons = new Map([ + // See /img/fontawesome/fontawesome-defs.svg + [ 'angle-up', { viewBox: '0 0 998 582', path: 'm 998,499 q 0,13 -10,23 l -50,50 q -10,10 -23,10 -13,0 -23,-10 L 499,179 106,572 Q 96,582 83,582 70,582 60,572 L 10,522 Q 0,512 0,499 0,486 10,476 L 476,10 q 10,-10 23,-10 13,0 23,10 l 466,466 q 10,10 10,23 z' } ], + [ 'arrow-right', { viewBox: '0 0 1472 1558', path: 'm 1472,779 q 0,54 -37,91 l -651,651 q -39,37 -91,37 -51,0 -90,-37 l -75,-75 q -38,-38 -38,-91 0,-53 38,-91 L 821,971 H 117 Q 65,971 32.5,933.5 0,896 0,843 V 715 Q 0,662 32.5,624.5 65,587 117,587 H 821 L 528,293 q -38,-36 -38,-90 0,-54 38,-90 l 75,-75 q 38,-38 90,-38 53,0 91,38 l 651,651 q 37,35 37,90 z' } ], + [ 'bar-chart', { viewBox: '0 0 2048 1536', path: 'm 640,768 0,512 -256,0 0,-512 256,0 z m 384,-512 0,1024 -256,0 0,-1024 256,0 z m 1024,1152 0,128 L 0,1536 0,0 l 128,0 0,1408 1920,0 z m -640,-896 0,768 -256,0 0,-768 256,0 z m 384,-384 0,1152 -256,0 0,-1152 256,0 z' } ], + [ 'bolt', { viewBox: '0 0 896 1664', path: 'm 885.08696,438 q 18,20 7,44 l -540,1157 q -13,25 -42,25 -4,0 -14,-2 -17,-5 -25.5,-19 -8.5,-14 -4.5,-30 l 197,-808 -406,101 q -4,1 -12,1 -18,0 -31,-11 Q -3.9130435,881 1.0869565,857 L 202.08696,32 q 4,-14 16,-23 12,-9 28,-9 l 328,0 q 19,0 32,12.5 13,12.5 13,29.5 0,8 -5,18 l -171,463 396,-98 q 8,-2 12,-2 19,0 34,15 z' } ], + [ 'clipboard', { viewBox: '0 0 1792 1792', path: 'm 768,1664 896,0 0,-640 -416,0 q -40,0 -68,-28 -28,-28 -28,-68 l 0,-416 -384,0 0,1152 z m 256,-1440 0,-64 q 0,-13 -9.5,-22.5 Q 1005,128 992,128 l -704,0 q -13,0 -22.5,9.5 Q 256,147 256,160 l 0,64 q 0,13 9.5,22.5 9.5,9.5 22.5,9.5 l 704,0 q 13,0 22.5,-9.5 9.5,-9.5 9.5,-22.5 z m 256,672 299,0 -299,-299 0,299 z m 512,128 0,672 q 0,40 -28,68 -28,28 -68,28 l -960,0 q -40,0 -68,-28 -28,-28 -28,-68 l 0,-160 -544,0 Q 56,1536 28,1508 0,1480 0,1440 L 0,96 Q 0,56 28,28 56,0 96,0 l 1088,0 q 40,0 68,28 28,28 28,68 l 0,328 q 21,13 36,28 l 408,408 q 28,28 48,76 20,48 20,88 z' } ], + [ 'clock-o', { viewBox: '0 0 1536 1536', path: 'm 896,416 v 448 q 0,14 -9,23 -9,9 -23,9 H 544 q -14,0 -23,-9 -9,-9 -9,-23 v -64 q 0,-14 9,-23 9,-9 23,-9 H 768 V 416 q 0,-14 9,-23 9,-9 23,-9 h 64 q 14,0 23,9 9,9 9,23 z m 416,352 q 0,-148 -73,-273 -73,-125 -198,-198 -125,-73 -273,-73 -148,0 -273,73 -125,73 -198,198 -73,125 -73,273 0,148 73,273 73,125 198,198 125,73 273,73 148,0 273,-73 125,-73 198,-198 73,-125 73,-273 z m 224,0 q 0,209 -103,385.5 Q 1330,1330 1153.5,1433 977,1536 768,1536 559,1536 382.5,1433 206,1330 103,1153.5 0,977 0,768 0,559 103,382.5 206,206 382.5,103 559,0 768,0 977,0 1153.5,103 1330,206 1433,382.5 1536,559 1536,768 Z' } ], + [ 'cloud-download', { viewBox: '0 0 1920 1408', path: 'm 1280,800 q 0,-14 -9,-23 -9,-9 -23,-9 l -224,0 0,-352 q 0,-13 -9.5,-22.5 Q 1005,384 992,384 l -192,0 q -13,0 -22.5,9.5 Q 768,403 768,416 l 0,352 -224,0 q -13,0 -22.5,9.5 -9.5,9.5 -9.5,22.5 0,14 9,23 l 352,352 q 9,9 23,9 14,0 23,-9 l 351,-351 q 10,-12 10,-24 z m 640,224 q 0,159 -112.5,271.5 Q 1695,1408 1536,1408 l -1088,0 Q 263,1408 131.5,1276.5 0,1145 0,960 0,830 70,720 140,610 258,555 256,525 256,512 256,300 406,150 556,0 768,0 q 156,0 285.5,87 129.5,87 188.5,231 71,-62 166,-62 106,0 181,75 75,75 75,181 0,76 -41,138 130,31 213.5,135.5 Q 1920,890 1920,1024 Z' } ], + [ 'cloud-upload', { viewBox: '0 0 1920 1408', path: 'm 1280,736 q 0,-14 -9,-23 L 919,361 q -9,-9 -23,-9 -14,0 -23,9 L 522,712 q -10,12 -10,24 0,14 9,23 9,9 23,9 l 224,0 0,352 q 0,13 9.5,22.5 9.5,9.5 22.5,9.5 l 192,0 q 13,0 22.5,-9.5 9.5,-9.5 9.5,-22.5 l 0,-352 224,0 q 13,0 22.5,-9.5 9.5,-9.5 9.5,-22.5 z m 640,288 q 0,159 -112.5,271.5 Q 1695,1408 1536,1408 l -1088,0 Q 263,1408 131.5,1276.5 0,1145 0,960 0,830 70,720 140,610 258,555 256,525 256,512 256,300 406,150 556,0 768,0 q 156,0 285.5,87 129.5,87 188.5,231 71,-62 166,-62 106,0 181,75 75,75 75,181 0,76 -41,138 130,31 213.5,135.5 Q 1920,890 1920,1024 Z' } ], + [ 'check', { viewBox: '0 0 1550 1188', path: 'm 1550,232 q 0,40 -28,68 l -724,724 -136,136 q -28,28 -68,28 -40,0 -68,-28 L 390,1024 28,662 Q 0,634 0,594 0,554 28,526 L 164,390 q 28,-28 68,-28 40,0 68,28 L 594,685 1250,28 q 28,-28 68,-28 40,0 68,28 l 136,136 q 28,28 28,68 z' } ], + [ 'code', { viewBox: '0 0 1830 1373', path: 'm 572,1125.5 -50,50 q -10,10 -23,10 -13,0 -23,-10 l -466,-466 q -10,-10 -10,-23 0,-13 10,-23 l 466,-466 q 10,-10 23,-10 13,0 23,10 l 50,50 q 10,10 10,23 0,13 -10,23 l -393,393 393,393 q 10,10 10,23 0,13 -10,23 z M 1163,58.476203 790,1349.4762 q -4,13 -15.5,19.5 -11.5,6.5 -23.5,2.5 l -62,-17 q -13,-4 -19.5,-15.5 -6.5,-11.5 -2.5,-24.5 L 1040,23.5 q 4,-13 15.5,-19.5 11.5,-6.5 23.5,-2.5 l 62,17 q 13,4 19.5,15.5 6.5,11.5 2.5,24.5 z m 657,651 -466,466 q -10,10 -23,10 -13,0 -23,-10 l -50,-50 q -10,-10 -10,-23 0,-13 10,-23 l 393,-393 -393,-393 q -10,-10 -10,-23 0,-13 10,-23 l 50,-50 q 10,-10 23,-10 13,0 23,10 l 466,466 q 10,10 10,23 0,13 -10,23 z' } ], + [ 'cog', { viewBox: '0 0 1536 1536', path: 'm 1024,768 q 0,-106 -75,-181 -75,-75 -181,-75 -106,0 -181,75 -75,75 -75,181 0,106 75,181 75,75 181,75 106,0 181,-75 75,-75 75,-181 z m 512,-109 0,222 q 0,12 -8,23 -8,11 -20,13 l -185,28 q -19,54 -39,91 35,50 107,138 10,12 10,25 0,13 -9,23 -27,37 -99,108 -72,71 -94,71 -12,0 -26,-9 l -138,-108 q -44,23 -91,38 -16,136 -29,186 -7,28 -36,28 l -222,0 q -14,0 -24.5,-8.5 Q 622,1519 621,1506 l -28,-184 q -49,-16 -90,-37 l -141,107 q -10,9 -25,9 -14,0 -25,-11 -126,-114 -165,-168 -7,-10 -7,-23 0,-12 8,-23 15,-21 51,-66.5 36,-45.5 54,-70.5 -27,-50 -41,-99 L 29,913 Q 16,911 8,900.5 0,890 0,877 L 0,655 q 0,-12 8,-23 8,-11 19,-13 l 186,-28 q 14,-46 39,-92 -40,-57 -107,-138 -10,-12 -10,-24 0,-10 9,-23 26,-36 98.5,-107.5 Q 315,135 337,135 q 13,0 26,10 L 501,252 Q 545,229 592,214 608,78 621,28 628,0 657,0 L 879,0 Q 893,0 903.5,8.5 914,17 915,30 l 28,184 q 49,16 90,37 l 142,-107 q 9,-9 24,-9 13,0 25,10 129,119 165,170 7,8 7,22 0,12 -8,23 -15,21 -51,66.5 -36,45.5 -54,70.5 26,50 41,98 l 183,28 q 13,2 21,12.5 8,10.5 8,23.5 z' } ], + [ 'cogs', { viewBox: '0 0 1920 1761', path: 'm 896,880 q 0,-106 -75,-181 -75,-75 -181,-75 -106,0 -181,75 -75,75 -75,181 0,106 75,181 75,75 181,75 106,0 181,-75 75,-75 75,-181 z m 768,512 q 0,-52 -38,-90 -38,-38 -90,-38 -52,0 -90,38 -38,38 -38,90 0,53 37.5,90.5 37.5,37.5 90.5,37.5 53,0 90.5,-37.5 37.5,-37.5 37.5,-90.5 z m 0,-1024 q 0,-52 -38,-90 -38,-38 -90,-38 -52,0 -90,38 -38,38 -38,90 0,53 37.5,90.5 37.5,37.5 90.5,37.5 53,0 90.5,-37.5 Q 1664,421 1664,368 Z m -384,421 v 185 q 0,10 -7,19.5 -7,9.5 -16,10.5 l -155,24 q -11,35 -32,76 34,48 90,115 7,11 7,20 0,12 -7,19 -23,30 -82.5,89.5 -59.5,59.5 -78.5,59.5 -11,0 -21,-7 l -115,-90 q -37,19 -77,31 -11,108 -23,155 -7,24 -30,24 H 547 q -11,0 -20,-7.5 -9,-7.5 -10,-17.5 l -23,-153 q -34,-10 -75,-31 l -118,89 q -7,7 -20,7 -11,0 -21,-8 -144,-133 -144,-160 0,-9 7,-19 10,-14 41,-53 31,-39 47,-61 -23,-44 -35,-82 L 24,1000 Q 14,999 7,990.5 0,982 0,971 V 786 Q 0,776 7,766.5 14,757 23,756 l 155,-24 q 11,-35 32,-76 -34,-48 -90,-115 -7,-11 -7,-20 0,-12 7,-20 22,-30 82,-89 60,-59 79,-59 11,0 21,7 l 115,90 q 34,-18 77,-32 11,-108 23,-154 7,-24 30,-24 h 186 q 11,0 20,7.5 9,7.5 10,17.5 l 23,153 q 34,10 75,31 l 118,-89 q 8,-7 20,-7 11,0 21,8 144,133 144,160 0,8 -7,19 -12,16 -42,54 -30,38 -45,60 23,48 34,82 l 152,23 q 10,2 17,10.5 7,8.5 7,19.5 z m 640,533 v 140 q 0,16 -149,31 -12,27 -30,52 51,113 51,138 0,4 -4,7 -122,71 -124,71 -8,0 -46,-47 -38,-47 -52,-68 -20,2 -30,2 -10,0 -30,-2 -14,21 -52,68 -38,47 -46,47 -2,0 -124,-71 -4,-3 -4,-7 0,-25 51,-138 -18,-25 -30,-52 -149,-15 -149,-31 v -140 q 0,-16 149,-31 13,-29 30,-52 -51,-113 -51,-138 0,-4 4,-7 4,-2 35,-20 31,-18 59,-34 28,-16 30,-16 8,0 46,46.5 38,46.5 52,67.5 20,-2 30,-2 10,0 30,2 51,-71 92,-112 l 6,-2 q 4,0 124,70 4,3 4,7 0,25 -51,138 17,23 30,52 149,15 149,31 z m 0,-1024 v 140 q 0,16 -149,31 -12,27 -30,52 51,113 51,138 0,4 -4,7 -122,71 -124,71 -8,0 -46,-47 -38,-47 -52,-68 -20,2 -30,2 -10,0 -30,-2 -14,21 -52,68 -38,47 -46,47 -2,0 -124,-71 -4,-3 -4,-7 0,-25 51,-138 -18,-25 -30,-52 -149,-15 -149,-31 V 298 q 0,-16 149,-31 13,-29 30,-52 -51,-113 -51,-138 0,-4 4,-7 4,-2 35,-20 31,-18 59,-34 28,-16 30,-16 8,0 46,46.5 38,46.5 52,67.5 20,-2 30,-2 10,0 30,2 51,-71 92,-112 l 6,-2 q 4,0 124,70 4,3 4,7 0,25 -51,138 17,23 30,52 149,15 149,31 z' } ], + [ 'comment-alt', { viewBox: '0 0 1792 1536', path: 'M 896,128 Q 692,128 514.5,197.5 337,267 232.5,385 128,503 128,640 128,752 199.5,853.5 271,955 401,1029 l 87,50 -27,96 q -24,91 -70,172 152,-63 275,-171 l 43,-38 57,6 q 69,8 130,8 204,0 381.5,-69.5 Q 1455,1013 1559.5,895 1664,777 1664,640 1664,503 1559.5,385 1455,267 1277.5,197.5 1100,128 896,128 Z m 896,512 q 0,174 -120,321.5 -120,147.5 -326,233 -206,85.5 -450,85.5 -70,0 -145,-8 -198,175 -460,242 -49,14 -114,22 h -5 q -15,0 -27,-10.5 -12,-10.5 -16,-27.5 v -1 q -3,-4 -0.5,-12 2.5,-8 2,-10 -0.5,-2 4.5,-9.5 l 6,-9 q 0,0 7,-8.5 7,-8.5 8,-9 7,-8 31,-34.5 24,-26.5 34.5,-38 10.5,-11.5 31,-39.5 20.5,-28 32.5,-51 12,-23 27,-59 15,-36 26,-76 Q 181,1052 90.5,921 0,790 0,640 0,466 120,318.5 240,171 446,85.5 652,0 896,0 q 244,0 450,85.5 206,85.5 326,233 120,147.5 120,321.5 z' } ], + [ 'double-angle-left', { viewBox: '0 0 966 998', path: 'm 582,915 q 0,13 -10,23 l -50,50 q -10,10 -23,10 -13,0 -23,-10 L 10,522 Q 0,512 0,499 0,486 10,476 L 476,10 q 10,-10 23,-10 13,0 23,10 l 50,50 q 10,10 10,23 0,13 -10,23 L 179,499 572,892 q 10,10 10,23 z m 384,0 q 0,13 -10,23 l -50,50 q -10,10 -23,10 -13,0 -23,-10 L 394,522 q -10,-10 -10,-23 0,-13 10,-23 L 860,10 q 10,-10 23,-10 13,0 23,10 l 50,50 q 10,10 10,23 0,13 -10,23 L 563,499 956,892 q 10,10 10,23 z' } ], + [ 'double-angle-up', { viewBox: '0 0 998 966', path: 'm 998,883 q 0,13 -10,23 l -50,50 q -10,10 -23,10 -13,0 -23,-10 L 499,563 106,956 Q 96,966 83,966 70,966 60,956 L 10,906 Q 0,896 0,883 0,870 10,860 L 476,394 q 10,-10 23,-10 13,0 23,10 l 466,466 q 10,10 10,23 z m 0,-384 q 0,13 -10,23 l -50,50 q -10,10 -23,10 -13,0 -23,-10 L 499,179 106,572 Q 96,582 83,582 70,582 60,572 L 10,522 Q 0,512 0,499 0,486 10,476 L 476,10 q 10,-10 23,-10 13,0 23,10 l 466,466 q 10,10 10,23 z' } ], + [ 'download-alt', { viewBox: '0 0 1664 1536', path: 'm 1280,1344 q 0,-26 -19,-45 -19,-19 -45,-19 -26,0 -45,19 -19,19 -19,45 0,26 19,45 19,19 45,19 26,0 45,-19 19,-19 19,-45 z m 256,0 q 0,-26 -19,-45 -19,-19 -45,-19 -26,0 -45,19 -19,19 -19,45 0,26 19,45 19,19 45,19 26,0 45,-19 19,-19 19,-45 z m 128,-224 v 320 q 0,40 -28,68 -28,28 -68,28 H 96 q -40,0 -68,-28 -28,-28 -28,-68 v -320 q 0,-40 28,-68 28,-28 68,-28 h 465 l 135,136 q 58,56 136,56 78,0 136,-56 l 136,-136 h 464 q 40,0 68,28 28,28 28,68 z M 1339,551 q 17,41 -14,70 l -448,448 q -18,19 -45,19 -27,0 -45,-19 L 339,621 q -31,-29 -14,-70 17,-39 59,-39 H 640 V 64 Q 640,38 659,19 678,0 704,0 h 256 q 26,0 45,19 19,19 19,45 v 448 h 256 q 42,0 59,39 z' } ], + [ 'eraser', { viewBox: '0 0 1920 1280', path: 'M 896,1152 1232,768 l -768,0 -336,384 768,0 z M 1909,75 q 15,34 9.5,71.5 Q 1913,184 1888,212 L 992,1236 q -38,44 -96,44 l -768,0 q -38,0 -69.5,-20.5 -31.5,-20.5 -47.5,-54.5 -15,-34 -9.5,-71.5 5.5,-37.5 30.5,-65.5 L 928,44 Q 966,0 1024,0 l 768,0 q 38,0 69.5,20.5 Q 1893,41 1909,75 Z' } ], + [ 'exclamation-triangle', { viewBox: '0 0 1794 1664', path: 'm 1025.0139,1375 0,-190 q 0,-14 -9.5,-23.5 -9.5,-9.5 -22.5,-9.5 l -192,0 q -13,0 -22.5,9.5 -9.5,9.5 -9.5,23.5 l 0,190 q 0,14 9.5,23.5 9.5,9.5 22.5,9.5 l 192,0 q 13,0 22.5,-9.5 9.5,-9.5 9.5,-23.5 z m -2,-374 18,-459 q 0,-12 -10,-19 -13,-11 -24,-11 l -220,0 q -11,0 -24,11 -10,7 -10,21 l 17,457 q 0,10 10,16.5 10,6.5 24,6.5 l 185,0 q 14,0 23.5,-6.5 9.5,-6.5 10.5,-16.5 z m -14,-934 768,1408 q 35,63 -2,126 -17,29 -46.5,46 -29.5,17 -63.5,17 l -1536,0 q -34,0 -63.5,-17 -29.5,-17 -46.5,-46 -37,-63 -2,-126 L 785.01389,67 q 17,-31 47,-49 30,-18 65,-18 35,0 65,18 30,18 47,49 z' } ], + [ 'external-link', { viewBox: '0 0 1792 1536', path: 'm 1408,928 0,320 q 0,119 -84.5,203.5 Q 1239,1536 1120,1536 l -832,0 Q 169,1536 84.5,1451.5 0,1367 0,1248 L 0,416 Q 0,297 84.5,212.5 169,128 288,128 l 704,0 q 14,0 23,9 9,9 9,23 l 0,64 q 0,14 -9,23 -9,9 -23,9 l -704,0 q -66,0 -113,47 -47,47 -47,113 l 0,832 q 0,66 47,113 47,47 113,47 l 832,0 q 66,0 113,-47 47,-47 47,-113 l 0,-320 q 0,-14 9,-23 9,-9 23,-9 l 64,0 q 14,0 23,9 9,9 9,23 z m 384,-864 0,512 q 0,26 -19,45 -19,19 -45,19 -26,0 -45,-19 L 1507,445 855,1097 q -10,10 -23,10 -13,0 -23,-10 L 695,983 q -10,-10 -10,-23 0,-13 10,-23 L 1347,285 1171,109 q -19,-19 -19,-45 0,-26 19,-45 19,-19 45,-19 l 512,0 q 26,0 45,19 19,19 19,45 z' } ], + [ 'eye-dropper', { viewBox: '0 0 1792 1792', path: 'm 1698,94 q 94,94 94,226.5 0,132.5 -94,225.5 l -225,223 104,104 q 10,10 10,23 0,13 -10,23 l -210,210 q -10,10 -23,10 -13,0 -23,-10 l -105,-105 -603,603 q -37,37 -90,37 l -203,0 -256,128 -64,-64 128,-256 0,-203 q 0,-53 37,-90 L 768,576 663,471 q -10,-10 -10,-23 0,-13 10,-23 L 873,215 q 10,-10 23,-10 13,0 23,10 L 1023,319 1246,94 Q 1339,0 1471.5,0 1604,0 1698,94 Z M 512,1472 1088,896 896,704 l -576,576 0,192 192,0 z' } ], + [ 'eye-open', { viewBox: '0 0 1792 1152', path: 'm 1664,576 q -152,-236 -381,-353 61,104 61,225 0,185 -131.5,316.5 Q 1081,896 896,896 711,896 579.5,764.5 448,633 448,448 448,327 509,223 280,340 128,576 261,781 461.5,902.5 662,1024 896,1024 1130,1024 1330.5,902.5 1531,781 1664,576 Z M 944,192 q 0,-20 -14,-34 -14,-14 -34,-14 -125,0 -214.5,89.5 Q 592,323 592,448 q 0,20 14,34 14,14 34,14 20,0 34,-14 14,-14 14,-34 0,-86 61,-147 61,-61 147,-61 20,0 34,-14 14,-14 14,-34 z m 848,384 q 0,34 -20,69 -140,230 -376.5,368.5 Q 1159,1152 896,1152 633,1152 396.5,1013 160,874 20,645 0,610 0,576 0,542 20,507 160,278 396.5,139 633,0 896,0 q 263,0 499.5,139 236.5,139 376.5,368 20,35 20,69 z' } ], + [ 'eye-slash', { viewBox: '0 0 1792 1344', path: 'M 555,1047 633,906 Q 546,843 497,747 448,651 448,544 448,423 509,319 280,436 128,672 295,930 555,1047 Z M 944,288 q 0,-20 -14,-34 -14,-14 -34,-14 -125,0 -214.5,89.5 Q 592,419 592,544 q 0,20 14,34 14,14 34,14 20,0 34,-14 14,-14 14,-34 0,-86 61,-147 61,-61 147,-61 20,0 34,-14 14,-14 14,-34 z M 1307,97 q 0,7 -1,9 -106,189 -316,567 -210,378 -315,566 l -49,89 q -10,16 -28,16 -12,0 -134,-70 -16,-10 -16,-28 0,-12 44,-87 Q 349,1094 228.5,986 108,878 20,741 0,710 0,672 0,634 20,603 173,368 400,232 627,96 896,96 q 89,0 180,17 l 54,-97 q 10,-16 28,-16 5,0 18,6 13,6 31,15.5 18,9.5 33,18.5 15,9 31.5,18.5 16.5,9.5 19.5,11.5 16,10 16,27 z m 37,447 q 0,139 -79,253.5 Q 1186,912 1056,962 l 280,-502 q 8,45 8,84 z m 448,128 q 0,35 -20,69 -39,64 -109,145 -150,172 -347.5,267 -197.5,95 -419.5,95 l 74,-132 Q 1182,1098 1362.5,979 1543,860 1664,672 1549,493 1382,378 l 63,-112 q 95,64 182.5,153 87.5,89 144.5,184 20,34 20,69 z' } ], + [ 'files-o', { viewBox: '0 0 1792 1792', path: 'm 1696,384 q 40,0 68,28 28,28 28,68 l 0,1216 q 0,40 -28,68 -28,28 -68,28 l -960,0 q -40,0 -68,-28 -28,-28 -28,-68 l 0,-288 -544,0 Q 56,1408 28,1380 0,1352 0,1312 L 0,640 Q 0,600 20,552 40,504 68,476 L 476,68 Q 504,40 552,20 600,0 640,0 l 416,0 q 40,0 68,28 28,28 28,68 l 0,328 q 68,-40 128,-40 l 416,0 z m -544,213 -299,299 299,0 0,-299 z M 512,213 213,512 l 299,0 0,-299 z m 196,647 316,-316 0,-416 -384,0 0,416 q 0,40 -28,68 -28,28 -68,28 l -416,0 0,640 512,0 0,-256 q 0,-40 20,-88 20,-48 48,-76 z m 956,804 0,-1152 -384,0 0,416 q 0,40 -28,68 -28,28 -68,28 l -416,0 0,640 896,0 z' } ], + [ 'film', { viewBox: '0 0 1920 1664', path: 'm 384,1472 0,-128 q 0,-26 -19,-45 -19,-19 -45,-19 l -128,0 q -26,0 -45,19 -19,19 -19,45 l 0,128 q 0,26 19,45 19,19 45,19 l 128,0 q 26,0 45,-19 19,-19 19,-45 z m 0,-384 0,-128 q 0,-26 -19,-45 -19,-19 -45,-19 l -128,0 q -26,0 -45,19 -19,19 -19,45 l 0,128 q 0,26 19,45 19,19 45,19 l 128,0 q 26,0 45,-19 19,-19 19,-45 z m 0,-384 0,-128 q 0,-26 -19,-45 -19,-19 -45,-19 l -128,0 q -26,0 -45,19 -19,19 -19,45 l 0,128 q 0,26 19,45 19,19 45,19 l 128,0 q 26,0 45,-19 19,-19 19,-45 z m 1024,768 0,-512 q 0,-26 -19,-45 -19,-19 -45,-19 l -768,0 q -26,0 -45,19 -19,19 -19,45 l 0,512 q 0,26 19,45 19,19 45,19 l 768,0 q 26,0 45,-19 19,-19 19,-45 z M 384,320 384,192 q 0,-26 -19,-45 -19,-19 -45,-19 l -128,0 q -26,0 -45,19 -19,19 -19,45 l 0,128 q 0,26 19,45 19,19 45,19 l 128,0 q 26,0 45,-19 19,-19 19,-45 z m 1408,1152 0,-128 q 0,-26 -19,-45 -19,-19 -45,-19 l -128,0 q -26,0 -45,19 -19,19 -19,45 l 0,128 q 0,26 19,45 19,19 45,19 l 128,0 q 26,0 45,-19 19,-19 19,-45 z m -384,-768 0,-512 q 0,-26 -19,-45 -19,-19 -45,-19 l -768,0 q -26,0 -45,19 -19,19 -19,45 l 0,512 q 0,26 19,45 19,19 45,19 l 768,0 q 26,0 45,-19 19,-19 19,-45 z m 384,384 0,-128 q 0,-26 -19,-45 -19,-19 -45,-19 l -128,0 q -26,0 -45,19 -19,19 -19,45 l 0,128 q 0,26 19,45 19,19 45,19 l 128,0 q 26,0 45,-19 19,-19 19,-45 z m 0,-384 0,-128 q 0,-26 -19,-45 -19,-19 -45,-19 l -128,0 q -26,0 -45,19 -19,19 -19,45 l 0,128 q 0,26 19,45 19,19 45,19 l 128,0 q 26,0 45,-19 19,-19 19,-45 z m 0,-384 0,-128 q 0,-26 -19,-45 -19,-19 -45,-19 l -128,0 q -26,0 -45,19 -19,19 -19,45 l 0,128 q 0,26 19,45 19,19 45,19 l 128,0 q 26,0 45,-19 19,-19 19,-45 z m 128,-160 0,1344 q 0,66 -47,113 -47,47 -113,47 l -1600,0 Q 94,1664 47,1617 0,1570 0,1504 L 0,160 Q 0,94 47,47 94,0 160,0 l 1600,0 q 66,0 113,47 47,47 47,113 z' } ], + [ 'filter', { viewBox: '0 0 1410 1408', path: 'm 1404.0208,39 q 17,41 -14,70 l -493,493 0,742 q 0,42 -39,59 -13,5 -25,5 -27,0 -45,-19 l -256,-256 q -19,-19 -19,-45 l 0,-486 L 20.020833,109 q -31,-29 -14,-70 Q 23.020833,0 65.020833,0 L 1345.0208,0 q 42,0 59,39 z' } ], + [ 'floppy-o', { viewBox: '0 0 1536 1536', path: 'm 384,1408 768,0 0,-384 -768,0 0,384 z m 896,0 128,0 0,-896 q 0,-14 -10,-38.5 Q 1388,449 1378,439 L 1097,158 q -10,-10 -34,-20 -24,-10 -39,-10 l 0,416 q 0,40 -28,68 -28,28 -68,28 l -576,0 q -40,0 -68,-28 -28,-28 -28,-68 l 0,-416 -128,0 0,1280 128,0 0,-416 q 0,-40 28,-68 28,-28 68,-28 l 832,0 q 40,0 68,28 28,28 28,68 l 0,416 z M 896,480 896,160 q 0,-13 -9.5,-22.5 Q 877,128 864,128 l -192,0 q -13,0 -22.5,9.5 Q 640,147 640,160 l 0,320 q 0,13 9.5,22.5 9.5,9.5 22.5,9.5 l 192,0 q 13,0 22.5,-9.5 Q 896,493 896,480 Z m 640,32 0,928 q 0,40 -28,68 -28,28 -68,28 L 96,1536 Q 56,1536 28,1508 0,1480 0,1440 L 0,96 Q 0,56 28,28 56,0 96,0 l 928,0 q 40,0 88,20 48,20 76,48 l 280,280 q 28,28 48,76 20,48 20,88 z' } ], + [ 'font', { viewBox: '0 0 1664 1536', path: 'M 725,431 555,881 q 33,0 136.5,2 103.5,2 160.5,2 19,0 57,-2 Q 822,630 725,431 Z M 0,1536 2,1457 q 23,-7 56,-12.5 33,-5.5 57,-10.5 24,-5 49.5,-14.5 25.5,-9.5 44.5,-29 19,-19.5 31,-50.5 L 477,724 757,0 l 75,0 53,0 q 8,14 11,21 l 205,480 q 33,78 106,257.5 73,179.5 114,274.5 15,34 58,144.5 43,110.5 72,168.5 20,45 35,57 19,15 88,29.5 69,14.5 84,20.5 6,38 6,57 0,5 -0.5,13.5 -0.5,8.5 -0.5,12.5 -63,0 -190,-8 -127,-8 -191,-8 -76,0 -215,7 -139,7 -178,8 0,-43 4,-78 l 131,-28 q 1,0 12.5,-2.5 11.5,-2.5 15.5,-3.5 4,-1 14.5,-4.5 10.5,-3.5 15,-6.5 4.5,-3 11,-8 6.5,-5 9,-11 2.5,-6 2.5,-14 0,-16 -31,-96.5 -31,-80.5 -72,-177.5 -41,-97 -42,-100 l -450,-2 q -26,58 -76.5,195.5 Q 382,1336 382,1361 q 0,22 14,37.5 14,15.5 43.5,24.5 29.5,9 48.5,13.5 19,4.5 57,8.5 38,4 41,4 1,19 1,58 0,9 -2,27 -58,0 -174.5,-10 -116.5,-10 -174.5,-10 -8,0 -26.5,4 -18.5,4 -21.5,4 -80,14 -188,14 z' } ], + [ 'home', { viewBox: '0 0 1612 1283', path: 'm 1382.1111,739 v 480 q 0,26 -19,45 -19,19 -45,19 H 934.11111 V 899 h -256 v 384 h -384 q -26,0 -45,-19 -19,-19 -19,-45 V 739 q 0,-1 0.5,-3 0.5,-2 0.5,-3 l 575,-474 574.99999,474 q 1,2 1,6 z m 223,-69 -62,74 q -8,9 -21,11 h -3 q -13,0 -21,-7 l -691.99999,-577 -692,577 q -12,8 -23.999999,7 -13,-2 -21,-11 L 7.1111111,670 Q -0.88888889,660 0.11111111,646.5 1.1111111,633 11.111111,625 L 730.11111,26 q 32,-26 76,-26 44,0 76,26 L 1126.1111,230 V 35 q 0,-14 9,-23 9,-9 23,-9 h 192 q 14,0 23,9 9,9 9,23 v 408 l 219,182 q 10,8 11,21.5 1,13.5 -7,23.5 z' } ], + [ 'info-circle', { viewBox: '0 0 1536 1536', path: 'm 1024,1248 0,-160 q 0,-14 -9,-23 -9,-9 -23,-9 l -96,0 0,-512 q 0,-14 -9,-23 -9,-9 -23,-9 l -320,0 q -14,0 -23,9 -9,9 -9,23 l 0,160 q 0,14 9,23 9,9 23,9 l 96,0 0,320 -96,0 q -14,0 -23,9 -9,9 -9,23 l 0,160 q 0,14 9,23 9,9 23,9 l 448,0 q 14,0 23,-9 9,-9 9,-23 z M 896,352 896,192 q 0,-14 -9,-23 -9,-9 -23,-9 l -192,0 q -14,0 -23,9 -9,9 -9,23 l 0,160 q 0,14 9,23 9,9 23,9 l 192,0 q 14,0 23,-9 9,-9 9,-23 z m 640,416 q 0,209 -103,385.5 Q 1330,1330 1153.5,1433 977,1536 768,1536 559,1536 382.5,1433 206,1330 103,1153.5 0,977 0,768 0,559 103,382.5 206,206 382.5,103 559,0 768,0 977,0 1153.5,103 1330,206 1433,382.5 1536,559 1536,768 Z' } ], + [ 'list-alt', { viewBox: '0 0 1792 1408', path: 'm 384,1056 0,64 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 l -64,0 q -13,0 -22.5,-9.5 Q 256,1133 256,1120 l 0,-64 q 0,-13 9.5,-22.5 9.5,-9.5 22.5,-9.5 l 64,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 0,-256 0,64 q 0,13 -9.5,22.5 Q 365,896 352,896 l -64,0 q -13,0 -22.5,-9.5 Q 256,877 256,864 l 0,-64 q 0,-13 9.5,-22.5 Q 275,768 288,768 l 64,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 0,-256 0,64 q 0,13 -9.5,22.5 Q 365,640 352,640 l -64,0 q -13,0 -22.5,-9.5 Q 256,621 256,608 l 0,-64 q 0,-13 9.5,-22.5 Q 275,512 288,512 l 64,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 1152,512 0,64 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 l -960,0 q -13,0 -22.5,-9.5 Q 512,1133 512,1120 l 0,-64 q 0,-13 9.5,-22.5 9.5,-9.5 22.5,-9.5 l 960,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 0,-256 0,64 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 l -960,0 q -13,0 -22.5,-9.5 Q 512,877 512,864 l 0,-64 q 0,-13 9.5,-22.5 Q 531,768 544,768 l 960,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 0,-256 0,64 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 l -960,0 q -13,0 -22.5,-9.5 Q 512,621 512,608 l 0,-64 q 0,-13 9.5,-22.5 Q 531,512 544,512 l 960,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 128,704 0,-832 q 0,-13 -9.5,-22.5 Q 1645,384 1632,384 l -1472,0 q -13,0 -22.5,9.5 Q 128,403 128,416 l 0,832 q 0,13 9.5,22.5 9.5,9.5 22.5,9.5 l 1472,0 q 13,0 22.5,-9.5 9.5,-9.5 9.5,-22.5 z m 128,-1088 0,1088 q 0,66 -47,113 -47,47 -113,47 l -1472,0 Q 94,1408 47,1361 0,1314 0,1248 L 0,160 Q 0,94 47,47 94,0 160,0 l 1472,0 q 66,0 113,47 47,47 47,113 z' } ], + [ 'lock', { viewBox: '0 0 1152 1408', path: 'm 320,640 512,0 0,-192 q 0,-106 -75,-181 -75,-75 -181,-75 -106,0 -181,75 -75,75 -75,181 l 0,192 z m 832,96 0,576 q 0,40 -28,68 -28,28 -68,28 l -960,0 Q 56,1408 28,1380 0,1352 0,1312 L 0,736 q 0,-40 28,-68 28,-28 68,-28 l 32,0 0,-192 Q 128,264 260,132 392,0 576,0 q 184,0 316,132 132,132 132,316 l 0,192 32,0 q 40,0 68,28 28,28 28,68 z' } ], + [ 'pause-circle-o', { viewBox: '0 0 1536 1536', path: 'M 768,0 Q 977,0 1153.5,103 1330,206 1433,382.5 1536,559 1536,768 1536,977 1433,1153.5 1330,1330 1153.5,1433 977,1536 768,1536 559,1536 382.5,1433 206,1330 103,1153.5 0,977 0,768 0,559 103,382.5 206,206 382.5,103 559,0 768,0 Z m 0,1312 q 148,0 273,-73 125,-73 198,-198 73,-125 73,-273 0,-148 -73,-273 -73,-125 -198,-198 -125,-73 -273,-73 -148,0 -273,73 -125,73 -198,198 -73,125 -73,273 0,148 73,273 73,125 198,198 125,73 273,73 z m 96,-224 q -14,0 -23,-9 -9,-9 -9,-23 l 0,-576 q 0,-14 9,-23 9,-9 23,-9 l 192,0 q 14,0 23,9 9,9 9,23 l 0,576 q 0,14 -9,23 -9,9 -23,9 l -192,0 z m -384,0 q -14,0 -23,-9 -9,-9 -9,-23 l 0,-576 q 0,-14 9,-23 9,-9 23,-9 l 192,0 q 14,0 23,9 9,9 9,23 l 0,576 q 0,14 -9,23 -9,9 -23,9 l -192,0 z' } ], + [ 'play-circle-o', { viewBox: '0 0 1536 1536', path: 'm 1184,768 q 0,37 -32,55 l -544,320 q -15,9 -32,9 -16,0 -32,-8 -32,-19 -32,-56 l 0,-640 q 0,-37 32,-56 33,-18 64,1 l 544,320 q 32,18 32,55 z m 128,0 q 0,-148 -73,-273 -73,-125 -198,-198 -125,-73 -273,-73 -148,0 -273,73 -125,73 -198,198 -73,125 -73,273 0,148 73,273 73,125 198,198 125,73 273,73 148,0 273,-73 125,-73 198,-198 73,-125 73,-273 z m 224,0 q 0,209 -103,385.5 Q 1330,1330 1153.5,1433 977,1536 768,1536 559,1536 382.5,1433 206,1330 103,1153.5 0,977 0,768 0,559 103,382.5 206,206 382.5,103 559,0 768,0 977,0 1153.5,103 1330,206 1433,382.5 1536,559 1536,768 Z' } ], + [ 'plus', { viewBox: '0 0 1408 1408', path: 'm 1408,608 0,192 q 0,40 -28,68 -28,28 -68,28 l -416,0 0,416 q 0,40 -28,68 -28,28 -68,28 l -192,0 q -40,0 -68,-28 -28,-28 -28,-68 l 0,-416 -416,0 Q 56,896 28,868 0,840 0,800 L 0,608 q 0,-40 28,-68 28,-28 68,-28 l 416,0 0,-416 Q 512,56 540,28 568,0 608,0 l 192,0 q 40,0 68,28 28,28 28,68 l 0,416 416,0 q 40,0 68,28 28,28 28,68 z' } ], + [ 'power-off', { viewBox: '0 0 1536 1664', path: 'm 1536,896 q 0,156 -61,298 -61,142 -164,245 -103,103 -245,164 -142,61 -298,61 -156,0 -298,-61 Q 328,1542 225,1439 122,1336 61,1194 0,1052 0,896 0,714 80.5,553 161,392 307,283 q 43,-32 95.5,-25 52.5,7 83.5,50 32,42 24.5,94.5 Q 503,455 461,487 363,561 309.5,668 256,775 256,896 q 0,104 40.5,198.5 40.5,94.5 109.5,163.5 69,69 163.5,109.5 94.5,40.5 198.5,40.5 104,0 198.5,-40.5 Q 1061,1327 1130,1258 1199,1189 1239.5,1094.5 1280,1000 1280,896 1280,775 1226.5,668 1173,561 1075,487 1033,455 1025.5,402.5 1018,350 1050,308 q 31,-43 84,-50 53,-7 95,25 146,109 226.5,270 80.5,161 80.5,343 z m -640,-768 0,640 q 0,52 -38,90 -38,38 -90,38 -52,0 -90,-38 -38,-38 -38,-90 l 0,-640 q 0,-52 38,-90 38,-38 90,-38 52,0 90,38 38,38 38,90 z' } ], + [ 'question-circle', { viewBox: '0 0 1536 1536', path: 'm 896,1248 v -192 q 0,-14 -9,-23 -9,-9 -23,-9 H 672 q -14,0 -23,9 -9,9 -9,23 v 192 q 0,14 9,23 9,9 23,9 h 192 q 14,0 23,-9 9,-9 9,-23 z m 256,-672 q 0,-88 -55.5,-163 Q 1041,338 958,297 875,256 788,256 q -243,0 -371,213 -15,24 8,42 l 132,100 q 7,6 19,6 16,0 25,-12 53,-68 86,-92 34,-24 86,-24 48,0 85.5,26 37.5,26 37.5,59 0,38 -20,61 -20,23 -68,45 -63,28 -115.5,86.5 Q 640,825 640,892 v 36 q 0,14 9,23 9,9 23,9 h 192 q 14,0 23,-9 9,-9 9,-23 0,-19 21.5,-49.5 Q 939,848 972,829 q 32,-18 49,-28.5 17,-10.5 46,-35 29,-24.5 44.5,-48 15.5,-23.5 28,-60.5 12.5,-37 12.5,-81 z m 384,192 q 0,209 -103,385.5 Q 1330,1330 1153.5,1433 977,1536 768,1536 559,1536 382.5,1433 206,1330 103,1153.5 0,977 0,768 0,559 103,382.5 206,206 382.5,103 559,0 768,0 977,0 1153.5,103 1330,206 1433,382.5 1536,559 1536,768 Z' } ], + [ 'refresh', { viewBox: '0 0 1536 1536', path: 'm 1511,928 q 0,5 -1,7 -64,268 -268,434.5 Q 1038,1536 764,1536 618,1536 481.5,1481 345,1426 238,1324 l -129,129 q -19,19 -45,19 -26,0 -45,-19 Q 0,1434 0,1408 L 0,960 q 0,-26 19,-45 19,-19 45,-19 l 448,0 q 26,0 45,19 19,19 19,45 0,26 -19,45 l -137,137 q 71,66 161,102 90,36 187,36 134,0 250,-65 116,-65 186,-179 11,-17 53,-117 8,-23 30,-23 l 192,0 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 25,-800 0,448 q 0,26 -19,45 -19,19 -45,19 l -448,0 q -26,0 -45,-19 -19,-19 -19,-45 0,-26 19,-45 L 1117,393 Q 969,256 768,256 q -134,0 -250,65 -116,65 -186,179 -11,17 -53,117 -8,23 -30,23 L 50,640 Q 37,640 27.5,630.5 18,621 18,608 l 0,-7 Q 83,333 288,166.5 493,0 768,0 914,0 1052,55.5 1190,111 1297,212 L 1427,83 q 19,-19 45,-19 26,0 45,19 19,19 19,45 z' } ], + [ 'save', { viewBox: '0 0 1536 1536', path: 'm 384,1408 h 768 V 1024 H 384 Z m 896,0 h 128 V 512 q 0,-14 -10,-38.5 Q 1388,449 1378,439 L 1097,158 q -10,-10 -34,-20 -24,-10 -39,-10 v 416 q 0,40 -28,68 -28,28 -68,28 H 352 q -40,0 -68,-28 -28,-28 -28,-68 V 128 H 128 V 1408 H 256 V 992 q 0,-40 28,-68 28,-28 68,-28 h 832 q 40,0 68,28 28,28 28,68 z M 896,480 V 160 q 0,-13 -9.5,-22.5 Q 877,128 864,128 H 672 q -13,0 -22.5,9.5 Q 640,147 640,160 v 320 q 0,13 9.5,22.5 9.5,9.5 22.5,9.5 h 192 q 13,0 22.5,-9.5 Q 896,493 896,480 Z m 640,32 v 928 q 0,40 -28,68 -28,28 -68,28 H 96 Q 56,1536 28,1508 0,1480 0,1440 V 96 Q 0,56 28,28 56,0 96,0 h 928 q 40,0 88,20 48,20 76,48 l 280,280 q 28,28 48,76 20,48 20,88 z' } ], + [ 'search', { viewBox: '0 0 1664 1664', path: 'M 1152,704 Q 1152,519 1020.5,387.5 889,256 704,256 519,256 387.5,387.5 256,519 256,704 256,889 387.5,1020.5 519,1152 704,1152 889,1152 1020.5,1020.5 1152,889 1152,704 Z m 512,832 q 0,52 -38,90 -38,38 -90,38 -54,0 -90,-38 L 1103,1284 Q 924,1408 704,1408 561,1408 430.5,1352.5 300,1297 205.5,1202.5 111,1108 55.5,977.5 0,847 0,704 0,561 55.5,430.5 111,300 205.5,205.5 300,111 430.5,55.5 561,0 704,0 q 143,0 273.5,55.5 130.5,55.5 225,150 94.5,94.5 150,225 55.5,130.5 55.5,273.5 0,220 -124,399 l 343,343 q 37,37 37,90 z' } ], + [ 'sliders', { viewBox: '0 0 1536 1408', path: 'm 352,1152 0,128 -352,0 0,-128 352,0 z m 352,-128 q 26,0 45,19 19,19 19,45 l 0,256 q 0,26 -19,45 -19,19 -45,19 l -256,0 q -26,0 -45,-19 -19,-19 -19,-45 l 0,-256 q 0,-26 19,-45 19,-19 45,-19 l 256,0 z m 160,-384 0,128 -864,0 0,-128 864,0 z m -640,-512 0,128 -224,0 0,-128 224,0 z m 1312,1024 0,128 -736,0 0,-128 736,0 z M 576,0 q 26,0 45,19 19,19 19,45 l 0,256 q 0,26 -19,45 -19,19 -45,19 l -256,0 q -26,0 -45,-19 -19,-19 -19,-45 L 256,64 Q 256,38 275,19 294,0 320,0 l 256,0 z m 640,512 q 26,0 45,19 19,19 19,45 l 0,256 q 0,26 -19,45 -19,19 -45,19 l -256,0 q -26,0 -45,-19 -19,-19 -19,-45 l 0,-256 q 0,-26 19,-45 19,-19 45,-19 l 256,0 z m 320,128 0,128 -224,0 0,-128 224,0 z m 0,-512 0,128 -864,0 0,-128 864,0 z' } ], + [ 'spinner', { viewBox: '0 0 1664 1728', path: 'm 462,1394 q 0,53 -37.5,90.5 -37.5,37.5 -90.5,37.5 -52,0 -90,-38 -38,-38 -38,-90 0,-53 37.5,-90.5 37.5,-37.5 90.5,-37.5 53,0 90.5,37.5 37.5,37.5 37.5,90.5 z m 498,206 q 0,53 -37.5,90.5 Q 885,1728 832,1728 779,1728 741.5,1690.5 704,1653 704,1600 q 0,-53 37.5,-90.5 37.5,-37.5 90.5,-37.5 53,0 90.5,37.5 Q 960,1547 960,1600 Z M 256,896 q 0,53 -37.5,90.5 Q 181,1024 128,1024 75,1024 37.5,986.5 0,949 0,896 0,843 37.5,805.5 75,768 128,768 q 53,0 90.5,37.5 Q 256,843 256,896 Z m 1202,498 q 0,52 -38,90 -38,38 -90,38 -53,0 -90.5,-37.5 -37.5,-37.5 -37.5,-90.5 0,-53 37.5,-90.5 37.5,-37.5 90.5,-37.5 53,0 90.5,37.5 37.5,37.5 37.5,90.5 z M 494,398 q 0,66 -47,113 -47,47 -113,47 -66,0 -113,-47 -47,-47 -47,-113 0,-66 47,-113 47,-47 113,-47 66,0 113,47 47,47 47,113 z m 1170,498 q 0,53 -37.5,90.5 -37.5,37.5 -90.5,37.5 -53,0 -90.5,-37.5 Q 1408,949 1408,896 q 0,-53 37.5,-90.5 37.5,-37.5 90.5,-37.5 53,0 90.5,37.5 Q 1664,843 1664,896 Z M 1024,192 q 0,80 -56,136 -56,56 -136,56 -80,0 -136,-56 -56,-56 -56,-136 0,-80 56,-136 56,-56 136,-56 80,0 136,56 56,56 56,136 z m 530,206 q 0,93 -66,158.5 -66,65.5 -158,65.5 -93,0 -158.5,-65.5 Q 1106,491 1106,398 q 0,-92 65.5,-158 65.5,-66 158.5,-66 92,0 158,66 66,66 66,158 z' } ], + [ 'times', { viewBox: '0 0 1188 1188', path: 'm 1188,956 q 0,40 -28,68 l -136,136 q -28,28 -68,28 -40,0 -68,-28 L 594,866 300,1160 q -28,28 -68,28 -40,0 -68,-28 L 28,1024 Q 0,996 0,956 0,916 28,888 L 322,594 28,300 Q 0,272 0,232 0,192 28,164 L 164,28 Q 192,0 232,0 272,0 300,28 L 594,322 888,28 q 28,-28 68,-28 40,0 68,28 l 136,136 q 28,28 28,68 0,40 -28,68 l -294,294 294,294 q 28,28 28,68 z' } ], + [ 'trash-o', { viewBox: '0 0 1408 1536', path: 'm 512,608 v 576 q 0,14 -9,23 -9,9 -23,9 h -64 q -14,0 -23,-9 -9,-9 -9,-23 V 608 q 0,-14 9,-23 9,-9 23,-9 h 64 q 14,0 23,9 9,9 9,23 z m 256,0 v 576 q 0,14 -9,23 -9,9 -23,9 h -64 q -14,0 -23,-9 -9,-9 -9,-23 V 608 q 0,-14 9,-23 9,-9 23,-9 h 64 q 14,0 23,9 9,9 9,23 z m 256,0 v 576 q 0,14 -9,23 -9,9 -23,9 h -64 q -14,0 -23,-9 -9,-9 -9,-23 V 608 q 0,-14 9,-23 9,-9 23,-9 h 64 q 14,0 23,9 9,9 9,23 z m 128,724 V 384 H 256 v 948 q 0,22 7,40.5 7,18.5 14.5,27 7.5,8.5 10.5,8.5 h 832 q 3,0 10.5,-8.5 7.5,-8.5 14.5,-27 7,-18.5 7,-40.5 z M 480,256 H 928 L 880,139 q -7,-9 -17,-11 H 546 q -10,2 -17,11 z m 928,32 v 64 q 0,14 -9,23 -9,9 -23,9 h -96 v 948 q 0,83 -47,143.5 -47,60.5 -113,60.5 H 288 q -66,0 -113,-58.5 Q 128,1419 128,1336 V 384 H 32 Q 18,384 9,375 0,366 0,352 v -64 q 0,-14 9,-23 9,-9 23,-9 H 341 L 411,89 Q 426,52 465,26 504,0 544,0 h 320 q 40,0 79,26 39,26 54,63 l 70,167 h 309 q 14,0 23,9 9,9 9,23 z' } ], + [ 'undo', { viewBox: '0 0 1536 1536', path: 'm 1536,768 q 0,156 -61,298 -61,142 -164,245 -103,103 -245,164 -142,61 -298,61 -172,0 -327,-72.5 Q 286,1391 177,1259 q -7,-10 -6.5,-22.5 0.5,-12.5 8.5,-20.5 l 137,-138 q 10,-9 25,-9 16,2 23,12 73,95 179,147 106,52 225,52 104,0 198.5,-40.5 Q 1061,1199 1130,1130 1199,1061 1239.5,966.5 1280,872 1280,768 1280,664 1239.5,569.5 1199,475 1130,406 1061,337 966.5,296.5 872,256 768,256 670,256 580,291.5 490,327 420,393 l 137,138 q 31,30 14,69 -17,40 -59,40 H 64 Q 38,640 19,621 0,602 0,576 V 128 Q 0,86 40,69 79,52 109,83 L 239,212 Q 346,111 483.5,55.5 621,0 768,0 q 156,0 298,61 142,61 245,164 103,103 164,245 61,142 61,298 z' } ], + [ 'unlink', { viewBox: '0 0 1664 1664', path: 'm 439,1271 -256,256 q -11,9 -23,9 -12,0 -23,-9 -9,-10 -9,-23 0,-13 9,-23 l 256,-256 q 10,-9 23,-9 13,0 23,9 9,10 9,23 0,13 -9,23 z m 169,41 v 320 q 0,14 -9,23 -9,9 -23,9 -14,0 -23,-9 -9,-9 -9,-23 v -320 q 0,-14 9,-23 9,-9 23,-9 14,0 23,9 9,9 9,23 z M 384,1088 q 0,14 -9,23 -9,9 -23,9 H 32 q -14,0 -23,-9 -9,-9 -9,-23 0,-14 9,-23 9,-9 23,-9 h 320 q 14,0 23,9 9,9 9,23 z m 1264,128 q 0,120 -85,203 l -147,146 q -83,83 -203,83 -121,0 -204,-85 L 675,1228 q -21,-21 -42,-56 l 239,-18 273,274 q 27,27 68,27.5 41,0.5 68,-26.5 l 147,-146 q 28,-28 28,-67 0,-40 -28,-68 l -274,-275 18,-239 q 35,21 56,42 l 336,336 q 84,86 84,204 z M 1031,492 792,510 519,236 q -28,-28 -68,-28 -39,0 -68,27 L 236,381 q -28,28 -28,67 0,40 28,68 l 274,274 -18,240 q -35,-21 -56,-42 L 100,652 Q 16,566 16,448 16,328 101,245 L 248,99 q 83,-83 203,-83 121,0 204,85 l 334,335 q 21,21 42,56 z m 633,84 q 0,14 -9,23 -9,9 -23,9 h -320 q -14,0 -23,-9 -9,-9 -9,-23 0,-14 9,-23 9,-9 23,-9 h 320 q 14,0 23,9 9,9 9,23 z M 1120,32 v 320 q 0,14 -9,23 -9,9 -23,9 -14,0 -23,-9 -9,-9 -9,-23 V 32 q 0,-14 9,-23 9,-9 23,-9 14,0 23,9 9,9 9,23 z m 407,151 -256,256 q -11,9 -23,9 -12,0 -23,-9 -9,-10 -9,-23 0,-13 9,-23 l 256,-256 q 10,-9 23,-9 13,0 23,9 9,10 9,23 0,13 -9,23 z' } ], + [ 'unlock-alt', { viewBox: '0 0 1152 1536', path: 'm 1056,768 q 40,0 68,28 28,28 28,68 v 576 q 0,40 -28,68 -28,28 -68,28 H 96 Q 56,1536 28,1508 0,1480 0,1440 V 864 q 0,-40 28,-68 28,-28 68,-28 h 32 V 448 Q 128,263 259.5,131.5 391,0 576,0 761,0 892.5,131.5 1024,263 1024,448 q 0,26 -19,45 -19,19 -45,19 h -64 q -26,0 -45,-19 -19,-19 -19,-45 0,-106 -75,-181 -75,-75 -181,-75 -106,0 -181,75 -75,75 -75,181 v 320 z' } ], + [ 'upload-alt', { viewBox: '0 0 1664 1600', path: 'm 1280,1408 q 0,-26 -19,-45 -19,-19 -45,-19 -26,0 -45,19 -19,19 -19,45 0,26 19,45 19,19 45,19 26,0 45,-19 19,-19 19,-45 z m 256,0 q 0,-26 -19,-45 -19,-19 -45,-19 -26,0 -45,19 -19,19 -19,45 0,26 19,45 19,19 45,19 26,0 45,-19 19,-19 19,-45 z m 128,-224 v 320 q 0,40 -28,68 -28,28 -68,28 H 96 q -40,0 -68,-28 -28,-28 -28,-68 v -320 q 0,-40 28,-68 28,-28 68,-28 h 427 q 21,56 70.5,92 49.5,36 110.5,36 h 256 q 61,0 110.5,-36 49.5,-36 70.5,-92 h 427 q 40,0 68,28 28,28 28,68 z M 1339,536 q -17,40 -59,40 h -256 v 448 q 0,26 -19,45 -19,19 -45,19 H 704 q -26,0 -45,-19 -19,-19 -19,-45 V 576 H 384 q -42,0 -59,-40 -17,-39 14,-69 L 787,19 q 18,-19 45,-19 27,0 45,19 l 448,448 q 31,30 14,69 z' } ], + [ 'zoom-in', { viewBox: '0 0 1664 1664', path: 'm 1024,672 v 64 q 0,13 -9.5,22.5 Q 1005,768 992,768 H 768 v 224 q 0,13 -9.5,22.5 -9.5,9.5 -22.5,9.5 h -64 q -13,0 -22.5,-9.5 Q 640,1005 640,992 V 768 H 416 q -13,0 -22.5,-9.5 Q 384,749 384,736 v -64 q 0,-13 9.5,-22.5 Q 403,640 416,640 H 640 V 416 q 0,-13 9.5,-22.5 Q 659,384 672,384 h 64 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 v 224 h 224 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 128,32 Q 1152,519 1020.5,387.5 889,256 704,256 519,256 387.5,387.5 256,519 256,704 256,889 387.5,1020.5 519,1152 704,1152 889,1152 1020.5,1020.5 1152,889 1152,704 Z m 512,832 q 0,53 -37.5,90.5 -37.5,37.5 -90.5,37.5 -54,0 -90,-38 L 1103,1284 Q 924,1408 704,1408 561,1408 430.5,1352.5 300,1297 205.5,1202.5 111,1108 55.5,977.5 0,847 0,704 0,561 55.5,430.5 111,300 205.5,205.5 300,111 430.5,55.5 561,0 704,0 q 143,0 273.5,55.5 130.5,55.5 225,150 94.5,94.5 150,225 55.5,130.5 55.5,273.5 0,220 -124,399 l 343,343 q 37,37 37,90 z' } ], + [ 'zoom-out', { viewBox: '0 0 1664 1664', path: 'm 1024,672 v 64 q 0,13 -9.5,22.5 Q 1005,768 992,768 H 416 q -13,0 -22.5,-9.5 Q 384,749 384,736 v -64 q 0,-13 9.5,-22.5 Q 403,640 416,640 h 576 q 13,0 22.5,9.5 9.5,9.5 9.5,22.5 z m 128,32 Q 1152,519 1020.5,387.5 889,256 704,256 519,256 387.5,387.5 256,519 256,704 256,889 387.5,1020.5 519,1152 704,1152 889,1152 1020.5,1020.5 1152,889 1152,704 Z m 512,832 q 0,53 -37.5,90.5 -37.5,37.5 -90.5,37.5 -54,0 -90,-38 L 1103,1284 Q 924,1408 704,1408 561,1408 430.5,1352.5 300,1297 205.5,1202.5 111,1108 55.5,977.5 0,847 0,704 0,561 55.5,430.5 111,300 205.5,205.5 300,111 430.5,55.5 561,0 704,0 q 143,0 273.5,55.5 130.5,55.5 225,150 94.5,94.5 150,225 55.5,130.5 55.5,273.5 0,220 -124,399 l 343,343 q 37,37 37,90 z' } ], + // See /img/photon.svg + [ 'ph-popups', { viewBox: '0 0 20 20', path: 'm 3.146,1.8546316 a 0.5006316,0.5006316 0 0 0 0.708,-0.708 l -1,-1 a 0.5006316,0.5006316 0 0 0 -0.708,0.708 z m -0.836,2.106 a 0.406,0.406 0 0 0 0.19,0.04 0.5,0.5 0 0 0 0.35,-0.851 0.493,0.493 0 0 0 -0.54,-0.109 0.361,0.361 0 0 0 -0.16,0.109 0.485,0.485 0 0 0 0,0.7 0.372,0.372 0 0 0 0.16,0.111 z m 3,-3 a 0.406,0.406 0 0 0 0.19,0.04 0.513,0.513 0 0 0 0.5,-0.5 0.473,0.473 0 0 0 -0.15,-0.351 0.5,0.5 0 0 0 -0.7,0 0.485,0.485 0 0 0 0,0.7 0.372,0.372 0 0 0 0.16,0.111 z m 13.19,1.04 a 0.5,0.5 0 0 0 0.354,-0.146 l 1,-1 a 0.5006316,0.5006316 0 0 0 -0.708,-0.708 l -1,1 a 0.5,0.5 0 0 0 0.354,0.854 z m 1.35,1.149 a 0.361,0.361 0 0 0 -0.16,-0.109 0.5,0.5 0 0 0 -0.38,0 0.361,0.361 0 0 0 -0.16,0.109 0.485,0.485 0 0 0 0,0.7 0.372,0.372 0 0 0 0.16,0.11 0.471,0.471 0 0 0 0.38,0 0.372,0.372 0 0 0 0.16,-0.11 0.469,0.469 0 0 0 0.15,-0.349 0.43,0.43 0 0 0 -0.04,-0.19 0.358,0.358 0 0 0 -0.11,-0.161 z m -3.54,-2.189 a 0.406,0.406 0 0 0 0.19,0.04 0.469,0.469 0 0 0 0.35,-0.15 0.353,0.353 0 0 0 0.11,-0.161 0.469,0.469 0 0 0 0,-0.379 0.358,0.358 0 0 0 -0.11,-0.161 0.361,0.361 0 0 0 -0.16,-0.109 0.493,0.493 0 0 0 -0.54,0.109 0.358,0.358 0 0 0 -0.11,0.161 0.43,0.43 0 0 0 -0.04,0.19 0.469,0.469 0 0 0 0.15,0.35 0.372,0.372 0 0 0 0.16,0.11 z m 2.544,15.1860004 a 0.5006316,0.5006316 0 0 0 -0.708,0.708 l 1,1 a 0.5006316,0.5006316 0 0 0 0.708,-0.708 z m 0.3,-2 a 0.473,0.473 0 0 0 -0.154,0.354 0.4,0.4 0 0 0 0.04,0.189 0.353,0.353 0 0 0 0.11,0.161 0.469,0.469 0 0 0 0.35,0.15 0.406,0.406 0 0 0 0.19,-0.04 0.372,0.372 0 0 0 0.16,-0.11 0.454,0.454 0 0 0 0.15,-0.35 0.473,0.473 0 0 0 -0.15,-0.351 0.5,0.5 0 0 0 -0.7,0 z m -3,3 a 0.473,0.473 0 0 0 -0.154,0.354 0.454,0.454 0 0 0 0.15,0.35 0.372,0.372 0 0 0 0.16,0.11 0.406,0.406 0 0 0 0.19,0.04 0.469,0.469 0 0 0 0.35,-0.15 0.353,0.353 0 0 0 0.11,-0.161 0.4,0.4 0 0 0 0.04,-0.189 0.473,0.473 0 0 0 -0.15,-0.351 0.5,0.5 0 0 0 -0.7,0 z M 18,5.0006316 a 3,3 0 0 0 -3,-3 H 7 a 3,3 0 0 0 -3,3 v 8.0000004 a 3,3 0 0 0 3,3 h 8 a 3,3 0 0 0 3,-3 z m -2,8.0000004 a 1,1 0 0 1 -1,1 H 7 a 1,1 0 0 1 -1,-1 V 7.0006316 H 16 Z M 16,6.0006316 H 6 v -1 a 1,1 0 0 1 1,-1 h 8 a 1,1 0 0 1 1,1 z M 11,18.000632 H 3 a 1,1 0 0 1 -1,-1 v -6 h 1 v -1 H 2 V 9.0006316 a 1,1 0 0 1 1,-1 v -2 a 3,3 0 0 0 -3,3 v 8.0000004 a 3,3 0 0 0 3,3 h 8 a 3,3 0 0 0 3,-3 h -2 a 1,1 0 0 1 -1,1 z' } ], + [ 'ph-readermode-text-size', { viewBox: '0 0 20 12.5', path: 'M 10.422,11.223 A 0.712,0.712 0 0 1 10.295,11.007 L 6.581,0 H 4.68 L 0.933,11.309 0,11.447 V 12.5 H 3.594 V 11.447 L 2.655,11.325 A 0.3,0.3 0 0 1 2.468,11.211 0.214,0.214 0 0 1 2.419,10.974 L 3.341,8.387 h 3.575 l 0.906,2.652 a 0.18,0.18 0 0 1 -0.016,0.18 0.217,0.217 0 0 1 -0.139,0.106 L 6.679,11.447 V 12.5 h 4.62 V 11.447 L 10.663,11.325 A 0.512,0.512 0 0 1 10.422,11.223 Z M 3.659,7.399 5.063,2.57 6.5,7.399 Z M 19.27,11.464 A 0.406,0.406 0 0 1 19.009,11.337 0.368,0.368 0 0 1 18.902,11.072 V 6.779 A 3.838,3.838 0 0 0 18.67,5.318 1.957,1.957 0 0 0 18.01,4.457 2.48,2.48 0 0 0 16.987,4.044 7.582,7.582 0 0 0 15.67,3.938 a 6.505,6.505 0 0 0 -1.325,0.139 5.2,5.2 0 0 0 -1.2,0.4 2.732,2.732 0 0 0 -0.864,0.624 1.215,1.215 0 0 0 -0.331,0.833 0.532,0.532 0 0 0 0.119,0.383 0.665,0.665 0 0 0 0.257,0.172 0.916,0.916 0 0 0 0.375,0.041 h 1.723 V 4.942 A 4.429,4.429 0 0 1 14.611,4.91 2.045,2.045 0 0 1 14.836,4.885 c 0.09,0 0.192,-0.008 0.306,-0.008 a 1.849,1.849 0 0 1 0.808,0.151 1.247,1.247 0 0 1 0.71,0.89 2.164,2.164 0 0 1 0.049,0.51 c 0,0.076 -0.008,0.152 -0.008,0.228 0,0.076 -0.008,0.139 -0.008,0.221 v 0.2 q -1.152,0.252 -1.976,0.489 a 12.973,12.973 0 0 0 -1.391,0.474 4.514,4.514 0 0 0 -0.91,0.485 2.143,2.143 0 0 0 -0.527,0.523 1.594,1.594 0 0 0 -0.245,0.592 3.739,3.739 0 0 0 -0.061,0.693 2.261,2.261 0 0 0 0.171,0.9 2.024,2.024 0 0 0 0.469,0.682 2.084,2.084 0 0 0 0.693,0.432 2.364,2.364 0 0 0 0.852,0.151 3.587,3.587 0 0 0 1.068,-0.159 6.441,6.441 0 0 0 1.835,-0.877 l 0.22,0.832 H 20 v -0.783 z m -2.588,-0.719 a 4.314,4.314 0 0 1 -0.5,0.188 5.909,5.909 0 0 1 -0.493,0.123 2.665,2.665 0 0 1 -0.543,0.057 1.173,1.173 0 0 1 -0.861,-0.363 1.166,1.166 0 0 1 -0.245,-0.392 1.357,1.357 0 0 1 -0.086,-0.486 1.632,1.632 0 0 1 0.123,-0.657 1.215,1.215 0 0 1 0.432,-0.5 3.151,3.151 0 0 1 0.837,-0.392 12.429,12.429 0 0 1 1.334,-0.334 z' } ], + ]); + + return function(root) { + const icons = (root || document).querySelectorAll('.fa-icon'); + if ( icons.length === 0 ) { return; } + const svgNS = 'http://www.w3.org/2000/svg'; + for ( const icon of icons ) { + if ( icon.firstChild === null || icon.firstChild.nodeType !== 3 ) { + continue; + } + const name = icon.firstChild.nodeValue.trim(); + if ( name === '' ) { continue; } + const svg = document.createElementNS(svgNS, 'svg'); + svg.classList.add('fa-icon_' + name); + const details = svgIcons.get(name); + if ( details === undefined ) { + let file; + if ( name.startsWith('ph-') ) { + file = 'photon'; + } else if ( name.startsWith('md-') ) { + file = 'material-design'; + } else { + continue; + } + const use = document.createElementNS(svgNS, 'use'); + use.setAttribute('href', `/img/${file}.svg#${name}`); + svg.appendChild(use); + } else { + svg.setAttribute('viewBox', details.viewBox); + const path = document.createElementNS(svgNS, 'path'); + path.setAttribute('d', details.path); + svg.appendChild(path); + } + icon.replaceChild(svg, icon.firstChild); + if ( icon.classList.contains('fa-icon-badged') ) { + const badge = document.createElement('span'); + badge.className = 'fa-icon-badge'; + icon.insertBefore(badge, icon.firstChild.nextSibling); + } + } + }; +})(); + +faIconsInit(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/filtering-context.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/filtering-context.js new file mode 100644 index 0000000..8d90d38 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/filtering-context.js @@ -0,0 +1,391 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2018-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 +*/ + +'use strict'; + +/******************************************************************************/ + +import { + hostnameFromURI, + domainFromHostname, + originFromURI, +} from './uri-utils.js'; + +/******************************************************************************/ + +// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType + +// Long term, convert code wherever possible to work with integer-based type +// values -- the assumption being that integer operations are faster than +// string operations. + +const NO_TYPE = 0; +const BEACON = 1 << 0; +const CSP_REPORT = 1 << 1; +const FONT = 1 << 2; +const IMAGE = 1 << 4; +const IMAGESET = 1 << 4; +const MAIN_FRAME = 1 << 5; +const MEDIA = 1 << 6; +const OBJECT = 1 << 7; +const OBJECT_SUBREQUEST = 1 << 7; +const PING = 1 << 8; +const SCRIPT = 1 << 9; +const STYLESHEET = 1 << 10; +const SUB_FRAME = 1 << 11; +const WEBSOCKET = 1 << 12; +const XMLHTTPREQUEST = 1 << 13; +const INLINE_FONT = 1 << 14; +const INLINE_SCRIPT = 1 << 15; +const OTHER = 1 << 16; +const FRAME_ANY = MAIN_FRAME | SUB_FRAME; +const FONT_ANY = FONT | INLINE_FONT; +const INLINE_ANY = INLINE_FONT | INLINE_SCRIPT; +const PING_ANY = BEACON | CSP_REPORT | PING; +const SCRIPT_ANY = SCRIPT | INLINE_SCRIPT; + +const typeStrToIntMap = { + 'no_type': NO_TYPE, + 'beacon': BEACON, + 'csp_report': CSP_REPORT, + 'font': FONT, + 'image': IMAGE, + 'imageset': IMAGESET, + 'main_frame': MAIN_FRAME, + 'media': MEDIA, + 'object': OBJECT, + 'object_subrequest': OBJECT_SUBREQUEST, + 'ping': PING, + 'script': SCRIPT, + 'stylesheet': STYLESHEET, + 'sub_frame': SUB_FRAME, + 'websocket': WEBSOCKET, + 'xmlhttprequest': XMLHTTPREQUEST, + 'inline-font': INLINE_FONT, + 'inline-script': INLINE_SCRIPT, + 'other': OTHER, +}; + +/******************************************************************************/ + +const FilteringContext = class { + constructor(other) { + if ( other instanceof FilteringContext ) { + return this.fromFilteringContext(other); + } + this.tstamp = 0; + this.realm = ''; + this.id = undefined; + this.itype = 0; + this.stype = undefined; + this.url = undefined; + this.aliasURL = undefined; + this.hostname = undefined; + this.domain = undefined; + this.docId = -1; + this.frameId = -1; + this.docOrigin = undefined; + this.docHostname = undefined; + this.docDomain = undefined; + this.tabId = undefined; + this.tabOrigin = undefined; + this.tabHostname = undefined; + this.tabDomain = undefined; + this.redirectURL = undefined; + this.filter = undefined; + } + + get type() { + return this.stype; + } + + set type(a) { + this.itype = typeStrToIntMap[a] || NO_TYPE; + this.stype = a; + } + + isDocument() { + return (this.itype & FRAME_ANY) !== 0; + } + + isFont() { + return (this.itype & FONT_ANY) !== 0; + } + + fromFilteringContext(other) { + this.realm = other.realm; + this.type = other.type; + this.url = other.url; + this.hostname = other.hostname; + this.domain = other.domain; + this.docId = other.docId; + this.frameId = other.frameId; + this.docOrigin = other.docOrigin; + this.docHostname = other.docHostname; + this.docDomain = other.docDomain; + this.tabId = other.tabId; + this.tabOrigin = other.tabOrigin; + this.tabHostname = other.tabHostname; + this.tabDomain = other.tabDomain; + this.redirectURL = other.redirectURL; + this.filter = undefined; + return this; + } + + fromDetails({ originURL, url, type }) { + this.setDocOriginFromURL(originURL) + .setURL(url) + .setType(type); + return this; + } + + duplicate() { + return (new FilteringContext(this)); + } + + setRealm(a) { + this.realm = a; + return this; + } + + setType(a) { + this.type = a; + return this; + } + + setURL(a) { + if ( a !== this.url ) { + this.hostname = this.domain = undefined; + this.url = a; + } + return this; + } + + getHostname() { + if ( this.hostname === undefined ) { + this.hostname = hostnameFromURI(this.url); + } + return this.hostname; + } + + setHostname(a) { + if ( a !== this.hostname ) { + this.domain = undefined; + this.hostname = a; + } + return this; + } + + getDomain() { + if ( this.domain === undefined ) { + this.domain = domainFromHostname(this.getHostname()); + } + return this.domain; + } + + setDomain(a) { + this.domain = a; + return this; + } + + getDocOrigin() { + if ( this.docOrigin === undefined ) { + this.docOrigin = this.tabOrigin; + } + return this.docOrigin; + } + + setDocOrigin(a) { + if ( a !== this.docOrigin ) { + this.docHostname = this.docDomain = undefined; + this.docOrigin = a; + } + return this; + } + + setDocOriginFromURL(a) { + return this.setDocOrigin(originFromURI(a)); + } + + getDocHostname() { + if ( this.docHostname === undefined ) { + this.docHostname = hostnameFromURI(this.getDocOrigin()); + } + return this.docHostname; + } + + setDocHostname(a) { + if ( a !== this.docHostname ) { + this.docDomain = undefined; + this.docHostname = a; + } + return this; + } + + getDocDomain() { + if ( this.docDomain === undefined ) { + this.docDomain = domainFromHostname(this.getDocHostname()); + } + return this.docDomain; + } + + setDocDomain(a) { + this.docDomain = a; + return this; + } + + // The idea is to minimize the amout of work done to figure out whether + // the resource is 3rd-party to the document. + is3rdPartyToDoc() { + let docDomain = this.getDocDomain(); + if ( docDomain === '' ) { docDomain = this.docHostname; } + if ( this.domain !== undefined && this.domain !== '' ) { + return this.domain !== docDomain; + } + const hostname = this.getHostname(); + if ( hostname.endsWith(docDomain) === false ) { return true; } + const i = hostname.length - docDomain.length; + if ( i === 0 ) { return false; } + return hostname.charCodeAt(i - 1) !== 0x2E /* '.' */; + } + + setTabId(a) { + this.tabId = a; + return this; + } + + getTabOrigin() { + return this.tabOrigin; + } + + setTabOrigin(a) { + if ( a !== this.tabOrigin ) { + this.tabHostname = this.tabDomain = undefined; + this.tabOrigin = a; + } + return this; + } + + setTabOriginFromURL(a) { + return this.setTabOrigin(originFromURI(a)); + } + + getTabHostname() { + if ( this.tabHostname === undefined ) { + this.tabHostname = hostnameFromURI(this.getTabOrigin()); + } + return this.tabHostname; + } + + setTabHostname(a) { + if ( a !== this.tabHostname ) { + this.tabDomain = undefined; + this.tabHostname = a; + } + return this; + } + + getTabDomain() { + if ( this.tabDomain === undefined ) { + this.tabDomain = domainFromHostname(this.getTabHostname()); + } + return this.tabDomain; + } + + setTabDomain(a) { + this.docDomain = a; + return this; + } + + // The idea is to minimize the amout of work done to figure out whether + // the resource is 3rd-party to the top document. + is3rdPartyToTab() { + let tabDomain = this.getTabDomain(); + if ( tabDomain === '' ) { tabDomain = this.tabHostname; } + if ( this.domain !== undefined && this.domain !== '' ) { + return this.domain !== tabDomain; + } + const hostname = this.getHostname(); + if ( hostname.endsWith(tabDomain) === false ) { return true; } + const i = hostname.length - tabDomain.length; + if ( i === 0 ) { return false; } + return hostname.charCodeAt(i - 1) !== 0x2E /* '.' */; + } + + setFilter(a) { + this.filter = a; + return this; + } + + pushFilter(a) { + if ( this.filter === undefined ) { + return this.setFilter(a); + } + if ( Array.isArray(this.filter) ) { + this.filter.push(a); + } else { + this.filter = [ this.filter, a ]; + } + return this; + } + + pushFilters(a) { + if ( this.filter === undefined ) { + return this.setFilter(a); + } + if ( Array.isArray(this.filter) ) { + this.filter.push(...a); + } else { + this.filter = [ this.filter, ...a ]; + } + return this; + } +}; + +/******************************************************************************/ + +FilteringContext.prototype.BEACON = FilteringContext.BEACON = BEACON; +FilteringContext.prototype.CSP_REPORT = FilteringContext.CSP_REPORT = CSP_REPORT; +FilteringContext.prototype.FONT = FilteringContext.FONT = FONT; +FilteringContext.prototype.IMAGE = FilteringContext.IMAGE = IMAGE; +FilteringContext.prototype.IMAGESET = FilteringContext.IMAGESET = IMAGESET; +FilteringContext.prototype.MAIN_FRAME = FilteringContext.MAIN_FRAME = MAIN_FRAME; +FilteringContext.prototype.MEDIA = FilteringContext.MEDIA = MEDIA; +FilteringContext.prototype.OBJECT = FilteringContext.OBJECT = OBJECT; +FilteringContext.prototype.OBJECT_SUBREQUEST = FilteringContext.OBJECT_SUBREQUEST = OBJECT_SUBREQUEST; +FilteringContext.prototype.PING = FilteringContext.PING = PING; +FilteringContext.prototype.SCRIPT = FilteringContext.SCRIPT = SCRIPT; +FilteringContext.prototype.STYLESHEET = FilteringContext.STYLESHEET = STYLESHEET; +FilteringContext.prototype.SUB_FRAME = FilteringContext.SUB_FRAME = SUB_FRAME; +FilteringContext.prototype.WEBSOCKET = FilteringContext.WEBSOCKET = WEBSOCKET; +FilteringContext.prototype.XMLHTTPREQUEST = FilteringContext.XMLHTTPREQUEST = XMLHTTPREQUEST; +FilteringContext.prototype.INLINE_FONT = FilteringContext.INLINE_FONT = INLINE_FONT; +FilteringContext.prototype.INLINE_SCRIPT = FilteringContext.INLINE_SCRIPT = INLINE_SCRIPT; +FilteringContext.prototype.OTHER = FilteringContext.OTHER = OTHER; +FilteringContext.prototype.FRAME_ANY = FilteringContext.FRAME_ANY = FRAME_ANY; +FilteringContext.prototype.FONT_ANY = FilteringContext.FONT_ANY = FONT_ANY; +FilteringContext.prototype.INLINE_ANY = FilteringContext.INLINE_ANY = INLINE_ANY; +FilteringContext.prototype.PING_ANY = FilteringContext.PING_ANY = PING_ANY; +FilteringContext.prototype.SCRIPT_ANY = FilteringContext.SCRIPT_ANY = SCRIPT_ANY; + +/******************************************************************************/ + +export { FilteringContext }; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/filtering-engines.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/filtering-engines.js new file mode 100644 index 0000000..9b64ed8 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/filtering-engines.js @@ -0,0 +1,50 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +'use strict'; + +/******************************************************************************/ + +import DynamicHostRuleFiltering from './dynamic-net-filtering.js'; +import DynamicSwitchRuleFiltering from './hnswitches.js'; +import DynamicURLRuleFiltering from './url-net-filtering.js'; + +/******************************************************************************/ + +const permanentFirewall = new DynamicHostRuleFiltering(); +const sessionFirewall = new DynamicHostRuleFiltering(); + +const permanentURLFiltering = new DynamicURLRuleFiltering(); +const sessionURLFiltering = new DynamicURLRuleFiltering(); + +const permanentSwitches = new DynamicSwitchRuleFiltering(); +const sessionSwitches = new DynamicSwitchRuleFiltering(); + +/******************************************************************************/ + +export { + permanentFirewall, + sessionFirewall, + permanentURLFiltering, + sessionURLFiltering, + permanentSwitches, + sessionSwitches, +}; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/hnswitches.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/hnswitches.js new file mode 100644 index 0000000..afc0e4f --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/hnswitches.js @@ -0,0 +1,289 @@ +/******************************************************************************* + + uBlock Origin - a Chromium browser extension to black/white list requests. + Copyright (C) 2015-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 +*/ + +/* jshint bitwise: false */ + +'use strict'; + +/******************************************************************************/ + +import punycode from '../lib/punycode.js'; + +import { decomposeHostname } from './uri-utils.js'; +import { LineIterator } from './text-utils.js'; + +/******************************************************************************/ + +const decomposedSource = []; + +// Object.create(null) is used below to eliminate worries about unexpected +// property names in prototype chain -- and this way we don't have to use +// hasOwnProperty() to avoid this. + +const switchBitOffsets = Object.create(null); +Object.assign(switchBitOffsets, { + 'no-strict-blocking': 0, + 'no-popups': 2, + 'no-cosmetic-filtering': 4, + 'no-remote-fonts': 6, + 'no-large-media': 8, + 'no-csp-reports': 10, + 'no-scripting': 12, +}); + +const switchStateToNameMap = Object.create(null); +Object.assign(switchStateToNameMap, { + '1': 'true', + '2': 'false', +}); + +const nameToSwitchStateMap = Object.create(null); +Object.assign(nameToSwitchStateMap, { + 'true': 1, + 'false': 2, + 'on': 1, + 'off': 2, +}); + +/******************************************************************************/ + +// For performance purpose, as simple test as possible +const reNotASCII = /[^\x20-\x7F]/; + +// http://tools.ietf.org/html/rfc5952 +// 4.3: "MUST be represented in lowercase" +// Also: http://en.wikipedia.org/wiki/IPv6_address#Literal_IPv6_addresses_in_network_resource_identifiers + +/******************************************************************************/ + +class DynamicSwitchRuleFiltering { + constructor() { + this.reset(); + } + + reset() { + this.switches = new Map(); + this.n = ''; + this.z = ''; + this.r = 0; + this.changed = true; + } + + assign(from) { + // Remove rules not in other + for ( const hn of this.switches.keys() ) { + if ( from.switches.has(hn) === false ) { + this.switches.delete(hn); + this.changed = true; + } + } + // Add/change rules in other + for ( const [hn, bits] of from.switches ) { + if ( this.switches.get(hn) !== bits ) { + this.switches.set(hn, bits); + this.changed = true; + } + } + } + + copyRules(from, srcHostname) { + const thisBits = this.switches.get(srcHostname); + const fromBits = from.switches.get(srcHostname); + if ( fromBits !== thisBits ) { + if ( fromBits !== undefined ) { + this.switches.set(srcHostname, fromBits); + } else { + this.switches.delete(srcHostname); + } + this.changed = true; + } + return this.changed; + } + + hasSameRules(other, srcHostname) { + return this.switches.get(srcHostname) === other.switches.get(srcHostname); + } + + toggle(switchName, hostname, newVal) { + const bitOffset = switchBitOffsets[switchName]; + if ( bitOffset === undefined ) { return false; } + if ( newVal === this.evaluate(switchName, hostname) ) { return false; } + let bits = this.switches.get(hostname) || 0; + bits &= ~(3 << bitOffset); + bits |= newVal << bitOffset; + if ( bits === 0 ) { + this.switches.delete(hostname); + } else { + this.switches.set(hostname, bits); + } + this.changed = true; + return true; + } + + toggleOneZ(switchName, hostname, newState) { + const bitOffset = switchBitOffsets[switchName]; + if ( bitOffset === undefined ) { return false; } + let state = this.evaluateZ(switchName, hostname); + if ( newState === state ) { return false; } + if ( newState === undefined ) { + newState = !state; + } + let bits = this.switches.get(hostname) || 0; + bits &= ~(3 << bitOffset); + if ( bits === 0 ) { + this.switches.delete(hostname); + } else { + this.switches.set(hostname, bits); + } + state = this.evaluateZ(switchName, hostname); + if ( state !== newState ) { + this.switches.set(hostname, bits | ((newState ? 1 : 2) << bitOffset)); + } + this.changed = true; + return true; + } + + toggleBranchZ(switchName, targetHostname, newState) { + this.toggleOneZ(switchName, targetHostname, newState); + + // Turn off all descendant switches, they will inherit the state of the + // branch's origin. + const targetLen = targetHostname.length; + for ( const hostname of this.switches.keys() ) { + if ( hostname === targetHostname ) { continue; } + if ( hostname.length <= targetLen ) { continue; } + if ( hostname.endsWith(targetHostname) === false ) { continue; } + if ( hostname.charAt(hostname.length - targetLen - 1) !== '.' ) { + continue; + } + this.toggle(switchName, hostname, 0); + } + + return this.changed; + } + + toggleZ(switchName, hostname, deep, newState) { + if ( deep === true ) { + return this.toggleBranchZ(switchName, hostname, newState); + } + return this.toggleOneZ(switchName, hostname, newState); + } + + // 0 = inherit from broader scope, up to default state + // 1 = non-default state + // 2 = forced default state (to override a broader non-default state) + + evaluate(switchName, hostname) { + const bits = this.switches.get(hostname); + if ( bits === undefined ) { return 0; } + let bitOffset = switchBitOffsets[switchName]; + if ( bitOffset === undefined ) { return 0; } + return (bits >>> bitOffset) & 3; + } + + evaluateZ(switchName, hostname) { + const bitOffset = switchBitOffsets[switchName]; + if ( bitOffset === undefined ) { + this.r = 0; + return false; + } + this.n = switchName; + for ( const shn of decomposeHostname(hostname, decomposedSource) ) { + let bits = this.switches.get(shn); + if ( bits === undefined ) { continue; } + bits = bits >>> bitOffset & 3; + if ( bits === 0 ) { continue; } + this.z = shn; + this.r = bits; + return bits === 1; + } + this.r = 0; + return false; + } + + toLogData() { + return { + source: 'switch', + result: this.r, + raw: `${this.n}: ${this.z} true` + }; + } + + toArray() { + const out = []; + for ( const hostname of this.switches.keys() ) { + const prettyHn = hostname.includes('xn--') && punycode + ? punycode.toUnicode(hostname) + : hostname; + for ( const switchName in switchBitOffsets ) { + if ( switchBitOffsets[switchName] === undefined ) { continue; } + const val = this.evaluate(switchName, hostname); + if ( val === 0 ) { continue; } + out.push(`${switchName}: ${prettyHn} ${switchStateToNameMap[val]}`); + } + } + return out; + } + + toString() { + return this.toArray().join('\n'); + } + + fromString(text, append) { + const lineIter = new LineIterator(text); + if ( append !== true ) { this.reset(); } + while ( lineIter.eot() === false ) { + this.addFromRuleParts(lineIter.next().trim().split(/\s+/)); + } + } + + validateRuleParts(parts) { + if ( parts.length < 3 ) { return; } + if ( parts[0].endsWith(':') === false ) { return; } + if ( nameToSwitchStateMap[parts[2]] === undefined ) { return; } + if ( reNotASCII.test(parts[1]) && punycode !== undefined ) { + parts[1] = punycode.toASCII(parts[1]); + } + return parts; + } + + addFromRuleParts(parts) { + if ( this.validateRuleParts(parts) === undefined ) { return false; } + const switchName = parts[0].slice(0, -1); + if ( switchBitOffsets[switchName] === undefined ) { return false; } + this.toggle(switchName, parts[1], nameToSwitchStateMap[parts[2]]); + return true; + } + + removeFromRuleParts(parts) { + if ( this.validateRuleParts(parts) !== undefined ) { + this.toggle(parts[0].slice(0, -1), parts[1], 0); + return true; + } + return false; + } +} + +/******************************************************************************/ + +export default DynamicSwitchRuleFiltering; + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/hntrie.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/hntrie.js new file mode 100644 index 0000000..f125b2e --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/hntrie.js @@ -0,0 +1,778 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2017-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 +*/ + +/* globals WebAssembly */ + +'use strict'; + +/******************************************************************************* + + The original prototype was to develop an idea I had about using jump indices + in a TypedArray for quickly matching hostnames (or more generally strings)[1]. + Once I had a working, un-optimized prototype, I realized I had ended up + with something formally named a "trie": , + hence the name. I have no idea whether the implementation here or one + resembling it has been done elsewhere. + + "HN" in HNTrieContainer stands for "HostName", because the trie is + specialized to deal with matching hostnames -- which is a bit more + complicated than matching plain strings. + + For example, `www.abc.com` is deemed matching `abc.com`, because the former + is a subdomain of the latter. The opposite is of course not true. + + The resulting read-only tries created as a result of using HNTrieContainer + are simply just typed arrays filled with integers. The matching algorithm is + just a matter of reading/comparing these integers, and further using them as + indices in the array as a way to move around in the trie. + + [1] To solve + + Since this trie is specialized for matching hostnames, the stored + strings are reversed internally, because of hostname comparison logic: + + Correct matching: + index 0123456 + abc.com + | + www.abc.com + index 01234567890 + + Incorrect matching (typically used for plain strings): + index 0123456 + abc.com + | + www.abc.com + index 01234567890 + + ------------------------------------------------------------------------------ + + 1st iteration: + - https://github.com/gorhill/uBlock/blob/ff58107dac3a32607f8113e39ed5015584506813/src/js/hntrie.js + - Suitable for small to medium set of hostnames + - One buffer per trie + + 2nd iteration: goal was to make matches() method wasm-able + - https://github.com/gorhill/uBlock/blob/c3b0fd31f64bd7ffecdd282fb1208fe07aac3eb0/src/js/hntrie.js + - Suitable for small to medium set of hostnames + - Distinct tries all share same buffer: + - Reduced memory footprint + - https://stackoverflow.com/questions/45803829/memory-overhead-of-typed-arrays-vs-strings/45808835#45808835 + - Reusing needle character lookups for all tries + - This significantly reduce the number of String.charCodeAt() calls + - Slightly improved creation time + + This is the 3rd iteration: goal was to make add() method wasm-able and + further improve memory/CPU efficiency. + + This 3rd iteration has the following new traits: + - Suitable for small to large set of hostnames + - Support multiple trie containers (instanciable) + - Designed to hold large number of hostnames + - Hostnames can be added at any time (instead of all at once) + - This means pre-sorting is no longer a requirement + - The trie is always compact + - There is no longer a need for a `vacuum` method + - This makes the add() method wasm-able + - It can return the exact hostname which caused the match + - serializable/unserializable available for fast loading + - Distinct trie reference support the iteration protocol, thus allowing + to extract all the hostnames in the trie + + Its primary purpose is to replace the use of Set() as a mean to hold + large number of hostnames (ex. FilterHostnameDict in static filtering + engine). + + A HNTrieContainer is mostly a large buffer in which distinct but related + tries are stored. The memory layout of the buffer is as follow: + + 0-254: needle being processed + 255: length of needle + 256-259: offset to start of trie data section (=> trie0) + 260-263: offset to end of trie data section (=> trie1) + 264-267: offset to start of character data section (=> char0) + 268-271: offset to end of character data section (=> char1) + 272: start of trie data section + +*/ + +const PAGE_SIZE = 65536; + // i32 / i8 +const TRIE0_SLOT = 256 >>> 2; // 64 / 256 +const TRIE1_SLOT = TRIE0_SLOT + 1; // 65 / 260 +const CHAR0_SLOT = TRIE0_SLOT + 2; // 66 / 264 +const CHAR1_SLOT = TRIE0_SLOT + 3; // 67 / 268 +const TRIE0_START = TRIE0_SLOT + 4 << 2; // 272 + +const roundToPageSize = v => (v + PAGE_SIZE-1) & ~(PAGE_SIZE-1); + +class HNTrieContainer { + + constructor() { + const len = PAGE_SIZE * 2; + this.buf = new Uint8Array(len); + this.buf32 = new Uint32Array(this.buf.buffer); + this.needle = ''; + this.buf32[TRIE0_SLOT] = TRIE0_START; + this.buf32[TRIE1_SLOT] = this.buf32[TRIE0_SLOT]; + this.buf32[CHAR0_SLOT] = len >>> 1; + this.buf32[CHAR1_SLOT] = this.buf32[CHAR0_SLOT]; + this.wasmMemory = null; + + this.lastStored = ''; + this.lastStoredLen = this.lastStoredIndex = 0; + } + + //-------------------------------------------------------------------------- + // Public methods + //-------------------------------------------------------------------------- + + reset(details) { + if ( + details instanceof Object && + typeof details.byteLength === 'number' && + typeof details.char0 === 'number' + ) { + if ( details.byteLength > this.buf.byteLength ) { + this.reallocateBuf(details.byteLength); + } + this.buf32[CHAR0_SLOT] = details.char0; + } + this.buf32[TRIE1_SLOT] = this.buf32[TRIE0_SLOT]; + this.buf32[CHAR1_SLOT] = this.buf32[CHAR0_SLOT]; + + this.lastStored = ''; + this.lastStoredLen = this.lastStoredIndex = 0; + } + + setNeedle(needle) { + if ( needle !== this.needle ) { + const buf = this.buf; + let i = needle.length; + if ( i > 255 ) { i = 255; } + buf[255] = i; + while ( i-- ) { + buf[i] = needle.charCodeAt(i); + } + this.needle = needle; + } + return this; + } + + matchesJS(iroot) { + const buf32 = this.buf32; + const buf8 = this.buf; + const char0 = buf32[CHAR0_SLOT]; + let ineedle = buf8[255]; + let icell = buf32[iroot+0]; + if ( icell === 0 ) { return -1; } + let c = 0, v = 0, i0 = 0, n = 0; + for (;;) { + if ( ineedle === 0 ) { return -1; } + ineedle -= 1; + c = buf8[ineedle]; + // find first segment with a first-character match + for (;;) { + v = buf32[icell+2]; + i0 = char0 + (v >>> 8); + if ( buf8[i0] === c ) { break; } + icell = buf32[icell+0]; + if ( icell === 0 ) { return -1; } + } + // all characters in segment must match + n = v & 0x7F; + if ( n > 1 ) { + n -= 1; + if ( n > ineedle ) { return -1; } + i0 += 1; + const i1 = i0 + n; + do { + ineedle -= 1; + if ( buf8[i0] !== buf8[ineedle] ) { return -1; } + i0 += 1; + } while ( i0 < i1 ); + } + // boundary at end of segment? + if ( (v & 0x80) !== 0 ) { + if ( ineedle === 0 || buf8[ineedle-1] === 0x2E /* '.' */ ) { + return ineedle; + } + } + // next segment + icell = buf32[icell+1]; + if ( icell === 0 ) { break; } + } + return -1; + } + + createTrie() { + // grow buffer if needed + if ( (this.buf32[CHAR0_SLOT] - this.buf32[TRIE1_SLOT]) < 12 ) { + this.growBuf(12, 0); + } + const iroot = this.buf32[TRIE1_SLOT] >>> 2; + this.buf32[TRIE1_SLOT] += 12; + this.buf32[iroot+0] = 0; + this.buf32[iroot+1] = 0; + this.buf32[iroot+2] = 0; + return iroot; + } + + createTrieFromIterable(hostnames) { + const itrie = this.createTrie(); + for ( const hn of hostnames ) { + if ( hn === '' ) { continue; } + this.setNeedle(hn).add(itrie); + } + return itrie; + } + + createTrieFromStoredDomainOpt(i, n) { + const itrie = this.createTrie(); + const jend = i + n; + let j = i, offset = 0, k = 0, c = 0; + while ( j !== jend ) { + offset = this.buf32[CHAR0_SLOT]; // Important + k = 0; + for (;;) { + if ( j === jend ) { break; } + c = this.buf[offset+j]; + j += 1; + if ( c === 0x7C /* '|' */ ) { break; } + if ( k === 255 ) { continue; } + this.buf[k] = c; + k += 1; + } + if ( k !== 0 ) { + this.buf[255] = k; + this.add(itrie); + } + } + this.needle = ''; // Important + this.buf[255] = 0; // Important + return itrie; + } + + dumpTrie(iroot) { + let hostnames = Array.from(this.trieIterator(iroot)); + if ( String.prototype.padStart instanceof Function ) { + const maxlen = Math.min( + hostnames.reduce((maxlen, hn) => Math.max(maxlen, hn.length), 0), + 64 + ); + hostnames = hostnames.map(hn => hn.padStart(maxlen)); + } + for ( const hn of hostnames ) { + console.log(hn); + } + } + + trieIterator(iroot) { + return { + value: undefined, + done: false, + next() { + if ( this.icell === 0 ) { + if ( this.forks.length === 0 ) { + this.value = undefined; + this.done = true; + return this; + } + this.charPtr = this.forks.pop(); + this.icell = this.forks.pop(); + } + for (;;) { + const idown = this.container.buf32[this.icell+0]; + if ( idown !== 0 ) { + this.forks.push(idown, this.charPtr); + } + const v = this.container.buf32[this.icell+2]; + let i0 = this.container.buf32[CHAR0_SLOT] + (v >>> 8); + const i1 = i0 + (v & 0x7F); + while ( i0 < i1 ) { + this.charPtr -= 1; + this.charBuf[this.charPtr] = this.container.buf[i0]; + i0 += 1; + } + this.icell = this.container.buf32[this.icell+1]; + if ( (v & 0x80) !== 0 ) { + return this.toHostname(); + } + } + }, + toHostname() { + this.value = this.textDecoder.decode( + new Uint8Array(this.charBuf.buffer, this.charPtr) + ); + return this; + }, + container: this, + icell: this.buf32[iroot], + charBuf: new Uint8Array(256), + charPtr: 256, + forks: [], + textDecoder: new TextDecoder(), + [Symbol.iterator]() { return this; }, + }; + } + + // TODO: + // Rework code to add from a string already present in the character + // buffer, i.e. not having to go through setNeedle() when adding a new + // hostname to a trie. This will require much work though, and probably + // changing the order in which string segments are stored in the + // character buffer. + addJS(iroot) { + let lhnchar = this.buf[255]; + if ( lhnchar === 0 ) { return 0; } + // grow buffer if needed + if ( + (this.buf32[CHAR0_SLOT] - this.buf32[TRIE1_SLOT]) < 24 || + (this.buf.length - this.buf32[CHAR1_SLOT]) < 256 + ) { + this.growBuf(24, 256); + } + let icell = this.buf32[iroot+0]; + // special case: first node in trie + if ( icell === 0 ) { + this.buf32[iroot+0] = this.addLeafCell(lhnchar); + return 1; + } + // + const char0 = this.buf32[CHAR0_SLOT]; + let isegchar, lsegchar, boundaryBit, inext; + // find a matching cell: move down + for (;;) { + const v = this.buf32[icell+2]; + let isegchar0 = char0 + (v >>> 8); + // if first character is no match, move to next descendant + if ( this.buf[isegchar0] !== this.buf[lhnchar-1] ) { + inext = this.buf32[icell+0]; + if ( inext === 0 ) { + this.buf32[icell+0] = this.addLeafCell(lhnchar); + return 1; + } + icell = inext; + continue; + } + // 1st character was tested + isegchar = 1; + lhnchar -= 1; + // find 1st mismatch in rest of segment + lsegchar = v & 0x7F; + if ( lsegchar !== 1 ) { + for (;;) { + if ( isegchar === lsegchar ) { break; } + if ( lhnchar === 0 ) { break; } + if ( this.buf[isegchar0+isegchar] !== this.buf[lhnchar-1] ) { break; } + isegchar += 1; + lhnchar -= 1; + } + } + boundaryBit = v & 0x80; + // all segment characters matched + if ( isegchar === lsegchar ) { + // needle remainder: no + if ( lhnchar === 0 ) { + // boundary: yes, already present + if ( boundaryBit !== 0 ) { return 0; } + // boundary: no, mark as boundary + this.buf32[icell+2] = v | 0x80; + } + // needle remainder: yes + else { + // remainder is at label boundary? if yes, no need to add + // the rest since the shortest match is always reported + if ( boundaryBit !== 0 ) { + if ( this.buf[lhnchar-1] === 0x2E /* '.' */ ) { return -1; } + } + inext = this.buf32[icell+1]; + if ( inext !== 0 ) { + icell = inext; + continue; + } + // add needle remainder + this.buf32[icell+1] = this.addLeafCell(lhnchar); + } + } + // some segment characters matched + else { + // split current cell + isegchar0 -= char0; + this.buf32[icell+2] = isegchar0 << 8 | isegchar; + inext = this.addCell( + 0, + this.buf32[icell+1], + isegchar0 + isegchar << 8 | boundaryBit | lsegchar - isegchar + ); + this.buf32[icell+1] = inext; + // needle remainder: yes, need new cell for remaining characters + if ( lhnchar !== 0 ) { + this.buf32[inext+0] = this.addLeafCell(lhnchar); + } + // needle remainder: no, need boundary cell + else { + this.buf32[icell+2] |= 0x80; + } + } + return 1; + } + } + + optimize() { + this.shrinkBuf(); + return { + byteLength: this.buf.byteLength, + char0: this.buf32[CHAR0_SLOT], + }; + } + + serialize(encoder) { + if ( encoder instanceof Object ) { + return encoder.encode( + this.buf32.buffer, + this.buf32[CHAR1_SLOT] + ); + } + return Array.from( + new Uint32Array( + this.buf32.buffer, + 0, + this.buf32[CHAR1_SLOT] + 3 >>> 2 + ) + ); + } + + unserialize(selfie, decoder) { + this.needle = ''; + const shouldDecode = typeof selfie === 'string'; + let byteLength = shouldDecode + ? decoder.decodeSize(selfie) + : selfie.length << 2; + if ( byteLength === 0 ) { return false; } + byteLength = roundToPageSize(byteLength); + if ( this.wasmMemory !== null ) { + const pageCountBefore = this.buf.length >>> 16; + const pageCountAfter = byteLength >>> 16; + if ( pageCountAfter > pageCountBefore ) { + this.wasmMemory.grow(pageCountAfter - pageCountBefore); + this.buf = new Uint8Array(this.wasmMemory.buffer); + this.buf32 = new Uint32Array(this.buf.buffer); + } + } else if ( byteLength > this.buf.length ) { + this.buf = new Uint8Array(byteLength); + this.buf32 = new Uint32Array(this.buf.buffer); + } + if ( shouldDecode ) { + decoder.decode(selfie, this.buf.buffer); + } else { + this.buf32.set(selfie); + } + return true; + } + + // The following *Hostname() methods can be used to store hostname strings + // outside the trie. This is useful to store/match hostnames which are + // not part of a collection, and yet still benefit from storing the strings + // into a trie container's character buffer. + // TODO: WASM version of matchesHostname() + + storeHostname(hn) { + let n = hn.length; + if ( n > 255 ) { + hn = hn.slice(-255); + n = 255; + } + if ( n === this.lastStoredLen && hn === this.lastStored ) { + return this.lastStoredIndex; + } + this.lastStored = hn; + this.lastStoredLen = n; + if ( (this.buf.length - this.buf32[CHAR1_SLOT]) < n ) { + this.growBuf(0, n); + } + const offset = this.buf32[CHAR1_SLOT]; + this.buf32[CHAR1_SLOT] = offset + n; + const buf8 = this.buf; + for ( let i = 0; i < n; i++ ) { + buf8[offset+i] = hn.charCodeAt(i); + } + return (this.lastStoredIndex = offset - this.buf32[CHAR0_SLOT]); + } + + extractHostname(i, n) { + const textDecoder = new TextDecoder(); + const offset = this.buf32[CHAR0_SLOT] + i; + return textDecoder.decode(this.buf.subarray(offset, offset + n)); + } + + storeDomainOpt(s) { + let n = s.length; + if ( n === this.lastStoredLen && s === this.lastStored ) { + return this.lastStoredIndex; + } + this.lastStored = s; + this.lastStoredLen = n; + if ( (this.buf.length - this.buf32[CHAR1_SLOT]) < n ) { + this.growBuf(0, n); + } + const offset = this.buf32[CHAR1_SLOT]; + this.buf32[CHAR1_SLOT] = offset + n; + const buf8 = this.buf; + for ( let i = 0; i < n; i++ ) { + buf8[offset+i] = s.charCodeAt(i); + } + return (this.lastStoredIndex = offset - this.buf32[CHAR0_SLOT]); + } + + extractDomainOpt(i, n) { + const textDecoder = new TextDecoder(); + const offset = this.buf32[CHAR0_SLOT] + i; + return textDecoder.decode(this.buf.subarray(offset, offset + n)); + } + + matchesHostname(hn, i, n) { + this.setNeedle(hn); + const buf8 = this.buf; + const hr = buf8[255]; + if ( n > hr ) { return false; } + const hl = hr - n; + const nl = this.buf32[CHAR0_SLOT] + i; + for ( let j = 0; j < n; j++ ) { + if ( buf8[nl+j] !== buf8[hl+j] ) { return false; } + } + return n === hr || hn.charCodeAt(hl-1) === 0x2E /* '.' */; + } + + async enableWASM(wasmModuleFetcher, path) { + if ( typeof WebAssembly === 'undefined' ) { return false; } + if ( this.wasmMemory instanceof WebAssembly.Memory ) { return true; } + const module = await getWasmModule(wasmModuleFetcher, path); + if ( module instanceof WebAssembly.Module === false ) { return false; } + const memory = new WebAssembly.Memory({ initial: 2 }); + const instance = await WebAssembly.instantiate(module, { + imports: { + memory, + growBuf: this.growBuf.bind(this, 24, 256) + } + }); + if ( instance instanceof WebAssembly.Instance === false ) { return false; } + this.wasmMemory = memory; + const curPageCount = memory.buffer.byteLength >>> 16; + const newPageCount = roundToPageSize(this.buf.byteLength) >>> 16; + if ( newPageCount > curPageCount ) { + memory.grow(newPageCount - curPageCount); + } + const buf = new Uint8Array(memory.buffer); + buf.set(this.buf); + this.buf = buf; + this.buf32 = new Uint32Array(this.buf.buffer); + this.matches = this.matchesWASM = instance.exports.matches; + this.add = this.addWASM = instance.exports.add; + return true; + } + + dumpInfo() { + return [ + `Buffer size (Uint8Array): ${this.buf32[CHAR1_SLOT].toLocaleString('en')}`, + `WASM: ${this.wasmMemory === null ? 'disabled' : 'enabled'}`, + ].join('\n'); + } + + //-------------------------------------------------------------------------- + // Private methods + //-------------------------------------------------------------------------- + + addCell(idown, iright, v) { + let icell = this.buf32[TRIE1_SLOT]; + this.buf32[TRIE1_SLOT] = icell + 12; + icell >>>= 2; + this.buf32[icell+0] = idown; + this.buf32[icell+1] = iright; + this.buf32[icell+2] = v; + return icell; + } + + addLeafCell(lsegchar) { + const r = this.buf32[TRIE1_SLOT] >>> 2; + let i = r; + while ( lsegchar > 127 ) { + this.buf32[i+0] = 0; + this.buf32[i+1] = i + 3; + this.buf32[i+2] = this.addSegment(lsegchar, lsegchar - 127); + lsegchar -= 127; + i += 3; + } + this.buf32[i+0] = 0; + this.buf32[i+1] = 0; + this.buf32[i+2] = this.addSegment(lsegchar, 0) | 0x80; + this.buf32[TRIE1_SLOT] = i + 3 << 2; + return r; + } + + addSegment(lsegchar, lsegend) { + if ( lsegchar === 0 ) { return 0; } + let char1 = this.buf32[CHAR1_SLOT]; + const isegchar = char1 - this.buf32[CHAR0_SLOT]; + let i = lsegchar; + do { + this.buf[char1++] = this.buf[--i]; + } while ( i !== lsegend ); + this.buf32[CHAR1_SLOT] = char1; + return isegchar << 8 | lsegchar - lsegend; + } + + growBuf(trieGrow, charGrow) { + const char0 = Math.max( + roundToPageSize(this.buf32[TRIE1_SLOT] + trieGrow), + this.buf32[CHAR0_SLOT] + ); + const char1 = char0 + this.buf32[CHAR1_SLOT] - this.buf32[CHAR0_SLOT]; + const bufLen = Math.max( + roundToPageSize(char1 + charGrow), + this.buf.length + ); + this.resizeBuf(bufLen, char0); + } + + shrinkBuf() { + // Can't shrink WebAssembly.Memory + if ( this.wasmMemory !== null ) { return; } + const char0 = this.buf32[TRIE1_SLOT] + 24; + const char1 = char0 + this.buf32[CHAR1_SLOT] - this.buf32[CHAR0_SLOT]; + const bufLen = char1 + 256; + this.resizeBuf(bufLen, char0); + } + + resizeBuf(bufLen, char0) { + bufLen = roundToPageSize(bufLen); + if ( bufLen === this.buf.length && char0 === this.buf32[CHAR0_SLOT] ) { + return; + } + const charDataLen = this.buf32[CHAR1_SLOT] - this.buf32[CHAR0_SLOT]; + if ( this.wasmMemory !== null ) { + const pageCount = (bufLen >>> 16) - (this.buf.byteLength >>> 16); + if ( pageCount > 0 ) { + this.wasmMemory.grow(pageCount); + this.buf = new Uint8Array(this.wasmMemory.buffer); + this.buf32 = new Uint32Array(this.wasmMemory.buffer); + } + } else if ( bufLen !== this.buf.length ) { + const newBuf = new Uint8Array(bufLen); + newBuf.set( + new Uint8Array( + this.buf.buffer, + 0, + this.buf32[TRIE1_SLOT] + ), + 0 + ); + newBuf.set( + new Uint8Array( + this.buf.buffer, + this.buf32[CHAR0_SLOT], + charDataLen + ), + char0 + ); + this.buf = newBuf; + this.buf32 = new Uint32Array(this.buf.buffer); + this.buf32[CHAR0_SLOT] = char0; + this.buf32[CHAR1_SLOT] = char0 + charDataLen; + } + if ( char0 !== this.buf32[CHAR0_SLOT] ) { + this.buf.set( + new Uint8Array( + this.buf.buffer, + this.buf32[CHAR0_SLOT], + charDataLen + ), + char0 + ); + this.buf32[CHAR0_SLOT] = char0; + this.buf32[CHAR1_SLOT] = char0 + charDataLen; + } + } + + reallocateBuf(newSize) { + newSize = roundToPageSize(newSize); + if ( newSize === this.buf.length ) { return; } + if ( this.wasmMemory === null ) { + const newBuf = new Uint8Array(newSize); + newBuf.set( + newBuf.length < this.buf.length + ? this.buf.subarray(0, newBuf.length) + : this.buf + ); + this.buf = newBuf; + } else { + const growBy = + ((newSize + 0xFFFF) >>> 16) - (this.buf.length >>> 16); + if ( growBy <= 0 ) { return; } + this.wasmMemory.grow(growBy); + this.buf = new Uint8Array(this.wasmMemory.buffer); + } + this.buf32 = new Uint32Array(this.buf.buffer); + } +} + +HNTrieContainer.prototype.matches = HNTrieContainer.prototype.matchesJS; +HNTrieContainer.prototype.matchesWASM = null; + +HNTrieContainer.prototype.add = HNTrieContainer.prototype.addJS; +HNTrieContainer.prototype.addWASM = null; + +/******************************************************************************/ + +// Code below is to attempt to load a WASM module which implements: +// +// - HNTrieContainer.add() +// - HNTrieContainer.matches() +// +// The WASM module is entirely optional, the JS implementations will be +// used should the WASM module be unavailable for whatever reason. + +const getWasmModule = (( ) => { + let wasmModulePromise; + + return async function(wasmModuleFetcher, path) { + if ( wasmModulePromise instanceof Promise ) { + return wasmModulePromise; + } + + // The wasm module will work only if CPU is natively little-endian, + // as we use native uint32 array in our js code. + const uint32s = new Uint32Array(1); + const uint8s = new Uint8Array(uint32s.buffer); + uint32s[0] = 1; + if ( uint8s[0] !== 1 ) { return; } + + wasmModulePromise = wasmModuleFetcher(`${path}hntrie`).catch(reason => { + console.info(reason); + }); + + return wasmModulePromise; + }; +})(); + +/******************************************************************************/ + +export default HNTrieContainer; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/html-filtering.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/html-filtering.js new file mode 100644 index 0000000..66d8460 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/html-filtering.js @@ -0,0 +1,455 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2017-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 +*/ + +'use strict'; + +/******************************************************************************/ + +import logger from './logger.js'; +import µb from './background.js'; +import { sessionFirewall } from './filtering-engines.js'; + +import { + StaticExtFilteringHostnameDB, + StaticExtFilteringSessionDB, +} from './static-ext-filtering-db.js'; + +/******************************************************************************/ + +const pselectors = new Map(); +const duplicates = new Set(); + +const filterDB = new StaticExtFilteringHostnameDB(2); +const sessionFilterDB = new StaticExtFilteringSessionDB(); + +let acceptedCount = 0; +let discardedCount = 0; +let docRegister; + +const htmlFilteringEngine = { + get acceptedCount() { + return acceptedCount; + }, + get discardedCount() { + return discardedCount; + }, + getFilterCount() { + return filterDB.size; + }, +}; + +const PSelectorHasTextTask = class { + constructor(task) { + let arg0 = task[1], arg1; + if ( Array.isArray(task[1]) ) { + arg1 = arg0[1]; arg0 = arg0[0]; + } + this.needle = new RegExp(arg0, arg1); + } + transpose(node, output) { + if ( this.needle.test(node.textContent) ) { + output.push(node); + } + } +}; + +const PSelectorIfTask = class { + constructor(task) { + this.pselector = new PSelector(task[1]); + } + transpose(node, output) { + if ( this.pselector.test(node) === this.target ) { + output.push(node); + } + } + get invalid() { + return this.pselector.invalid; + } +}; +PSelectorIfTask.prototype.target = true; + +const PSelectorIfNotTask = class extends PSelectorIfTask { +}; +PSelectorIfNotTask.prototype.target = false; + +const PSelectorMinTextLengthTask = class { + constructor(task) { + this.min = task[1]; + } + transpose(node, output) { + if ( node.textContent.length >= this.min ) { + output.push(node); + } + } +}; + +const PSelectorSpathTask = class { + constructor(task) { + this.spath = task[1]; + } + transpose(node, output) { + const parent = node.parentElement; + if ( parent === null ) { return; } + let pos = 1; + for (;;) { + node = node.previousElementSibling; + if ( node === null ) { break; } + pos += 1; + } + const nodes = parent.querySelectorAll( + `:scope > :nth-child(${pos})${this.spath}` + ); + for ( const node of nodes ) { + output.push(node); + } + } +}; + +const PSelectorUpwardTask = class { + constructor(task) { + const arg = task[1]; + if ( typeof arg === 'number' ) { + this.i = arg; + } else { + this.s = arg; + } + } + transpose(node, output) { + if ( this.s !== '' ) { + const parent = node.parentElement; + if ( parent === null ) { return; } + node = parent.closest(this.s); + if ( node === null ) { return; } + } else { + let nth = this.i; + for (;;) { + node = node.parentElement; + if ( node === null ) { return; } + nth -= 1; + if ( nth === 0 ) { break; } + } + } + output.push(node); + } +}; +PSelectorUpwardTask.prototype.i = 0; +PSelectorUpwardTask.prototype.s = ''; + +const PSelectorXpathTask = class { + constructor(task) { + this.xpe = task[1]; + } + transpose(node, output) { + const xpr = docRegister.evaluate( + this.xpe, + node, + null, + XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, + null + ); + let j = xpr.snapshotLength; + while ( j-- ) { + const node = xpr.snapshotItem(j); + if ( node.nodeType === 1 ) { + output.push(node); + } + } + } +}; + +const PSelector = class { + constructor(o) { + this.raw = o.raw; + this.selector = o.selector; + this.tasks = []; + if ( !o.tasks ) { return; } + for ( const task of o.tasks ) { + const ctor = this.operatorToTaskMap.get(task[0]); + if ( ctor === undefined ) { + this.invalid = true; + break; + } + const pselector = new ctor(task); + if ( pselector instanceof PSelectorIfTask && pselector.invalid ) { + this.invalid = true; + break; + } + this.tasks.push(pselector); + } + } + prime(input) { + const root = input || docRegister; + if ( this.selector === '' ) { return [ root ]; } + return Array.from(root.querySelectorAll(this.selector)); + } + exec(input) { + if ( this.invalid ) { return []; } + let nodes = this.prime(input); + for ( const task of this.tasks ) { + if ( nodes.length === 0 ) { break; } + const transposed = []; + for ( const node of nodes ) { + task.transpose(node, transposed); + } + nodes = transposed; + } + return nodes; + } + test(input) { + if ( this.invalid ) { return false; } + const nodes = this.prime(input); + for ( const node of nodes ) { + let output = [ node ]; + for ( const task of this.tasks ) { + const transposed = []; + for ( const node of output ) { + task.transpose(node, transposed); + } + output = transposed; + if ( output.length === 0 ) { break; } + } + if ( output.length !== 0 ) { return true; } + } + return false; + } +}; +PSelector.prototype.operatorToTaskMap = new Map([ + [ ':has', PSelectorIfTask ], + [ ':has-text', PSelectorHasTextTask ], + [ ':if', PSelectorIfTask ], + [ ':if-not', PSelectorIfNotTask ], + [ ':min-text-length', PSelectorMinTextLengthTask ], + [ ':not', PSelectorIfNotTask ], + [ ':nth-ancestor', PSelectorUpwardTask ], + [ ':spath', PSelectorSpathTask ], + [ ':upward', PSelectorUpwardTask ], + [ ':xpath', PSelectorXpathTask ], +]); +PSelector.prototype.invalid = false; + +const logOne = function(details, exception, selector) { + µb.filteringContext + .duplicate() + .fromTabId(details.tabId) + .setRealm('extended') + .setType('dom') + .setURL(details.url) + .setDocOriginFromURL(details.url) + .setFilter({ + source: 'extended', + raw: `${exception === 0 ? '##' : '#@#'}^${selector}` + }) + .toLogger(); +}; + +const applyProceduralSelector = function(details, selector) { + let pselector = pselectors.get(selector); + if ( pselector === undefined ) { + pselector = new PSelector(JSON.parse(selector)); + pselectors.set(selector, pselector); + } + const nodes = pselector.exec(); + let modified = false; + for ( const node of nodes ) { + node.remove(); + modified = true; + } + if ( modified && logger.enabled ) { + logOne(details, 0, pselector.raw); + } + return modified; +}; + +const applyCSSSelector = function(details, selector) { + const nodes = docRegister.querySelectorAll(selector); + let modified = false; + for ( const node of nodes ) { + node.remove(); + modified = true; + } + if ( modified && logger.enabled ) { + logOne(details, 0, selector); + } + return modified; +}; + +htmlFilteringEngine.reset = function() { + filterDB.clear(); + pselectors.clear(); + duplicates.clear(); + acceptedCount = 0; + discardedCount = 0; +}; + +htmlFilteringEngine.freeze = function() { + duplicates.clear(); + filterDB.collectGarbage(); +}; + +htmlFilteringEngine.compile = function(parser, writer) { + const { raw, compiled, exception } = parser.result; + if ( compiled === undefined ) { + const who = writer.properties.get('name') || '?'; + logger.writeOne({ + realm: 'message', + type: 'error', + text: `Invalid HTML filter in ${who}: ##${raw}` + }); + return; + } + + writer.select('HTML_FILTERS'); + + // TODO: Mind negated hostnames, they are currently discarded. + + for ( const { hn, not, bad } of parser.extOptions() ) { + if ( bad ) { continue; } + let kind = 0; + if ( exception ) { + if ( not ) { continue; } + kind |= 0b01; + } + if ( compiled.charCodeAt(0) === 0x7B /* '{' */ ) { + kind |= 0b10; + } + writer.push([ 64, hn, kind, compiled ]); + } +}; + +htmlFilteringEngine.compileTemporary = function(parser) { + return { + session: sessionFilterDB, + selector: parser.result.compiled, + }; +}; + +htmlFilteringEngine.fromCompiledContent = function(reader) { + // Don't bother loading filters if stream filtering is not supported. + if ( µb.canFilterResponseData === false ) { return; } + + reader.select('HTML_FILTERS'); + + while ( reader.next() ) { + acceptedCount += 1; + const fingerprint = reader.fingerprint(); + if ( duplicates.has(fingerprint) ) { + discardedCount += 1; + continue; + } + duplicates.add(fingerprint); + const args = reader.args(); + filterDB.store(args[1], args[2], args[3]); + } +}; + +htmlFilteringEngine.getSession = function() { + return sessionFilterDB; +}; + +htmlFilteringEngine.retrieve = function(details) { + const hostname = details.hostname; + + const plains = new Set(); + const procedurals = new Set(); + const exceptions = new Set(); + + if ( sessionFilterDB.isNotEmpty ) { + sessionFilterDB.retrieve([ null, exceptions ]); + } + filterDB.retrieve( + hostname, + [ plains, exceptions, procedurals, exceptions ] + ); + const entity = details.entity !== '' + ? `${hostname.slice(0, -details.domain.length)}${details.entity}` + : '*'; + filterDB.retrieve( + entity, + [ plains, exceptions, procedurals, exceptions ], + 1 + ); + + if ( plains.size === 0 && procedurals.size === 0 ) { return; } + + // https://github.com/gorhill/uBlock/issues/2835 + // Do not filter if the site is under an `allow` rule. + if ( + µb.userSettings.advancedUserEnabled && + sessionFirewall.evaluateCellZY(hostname, hostname, '*') === 2 + ) { + return; + } + + const out = { plains, procedurals }; + + if ( exceptions.size === 0 ) { + return out; + } + + for ( const selector of exceptions ) { + if ( plains.has(selector) ) { + plains.delete(selector); + logOne(details, 1, selector); + continue; + } + if ( procedurals.has(selector) ) { + procedurals.delete(selector); + logOne(details, 1, JSON.parse(selector).raw); + continue; + } + } + + if ( plains.size !== 0 || procedurals.size !== 0 ) { + return out; + } +}; + +htmlFilteringEngine.apply = function(doc, details) { + docRegister = doc; + let modified = false; + for ( const selector of details.selectors.plains ) { + if ( applyCSSSelector(details, selector) ) { + modified = true; + } + } + for ( const selector of details.selectors.procedurals ) { + if ( applyProceduralSelector(details, selector) ) { + modified = true; + } + } + docRegister = undefined; + return modified; +}; + +htmlFilteringEngine.toSelfie = function() { + return filterDB.toSelfie(); +}; + +htmlFilteringEngine.fromSelfie = function(selfie) { + filterDB.fromSelfie(selfie); + pselectors.clear(); +}; + +/******************************************************************************/ + +export default htmlFilteringEngine; + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/httpheader-filtering.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/httpheader-filtering.js new file mode 100644 index 0000000..c959e56 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/httpheader-filtering.js @@ -0,0 +1,230 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2021-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 +*/ + +'use strict'; + +/******************************************************************************/ + +import logger from './logger.js'; +import µb from './background.js'; +import { entityFromDomain } from './uri-utils.js'; +import { sessionFirewall } from './filtering-engines.js'; + +import { + StaticExtFilteringHostnameDB, + StaticExtFilteringSessionDB, +} from './static-ext-filtering-db.js'; + +/******************************************************************************/ + +const duplicates = new Set(); +const filterDB = new StaticExtFilteringHostnameDB(1); +const sessionFilterDB = new StaticExtFilteringSessionDB(); + +const $headers = new Set(); +const $exceptions = new Set(); + +let acceptedCount = 0; +let discardedCount = 0; + +const headerIndexFromName = function(name, headers) { + let i = headers.length; + while ( i-- ) { + if ( headers[i].name.toLowerCase() === name ) { + return i; + } + } + return -1; +}; + +const logOne = function(isException, token, fctxt) { + fctxt.duplicate() + .setRealm('extended') + .setType('header') + .setFilter({ + modifier: true, + result: isException ? 2 : 1, + source: 'extended', + raw: `${(isException ? '#@#' : '##')}^responseheader(${token})` + }) + .toLogger(); +}; + +const httpheaderFilteringEngine = { + get acceptedCount() { + return acceptedCount; + }, + get discardedCount() { + return discardedCount; + } +}; + +httpheaderFilteringEngine.reset = function() { + filterDB.clear(); + duplicates.clear(); + acceptedCount = 0; + discardedCount = 0; +}; + +httpheaderFilteringEngine.freeze = function() { + duplicates.clear(); + filterDB.collectGarbage(); +}; + +httpheaderFilteringEngine.compile = function(parser, writer) { + writer.select('HTTPHEADER_FILTERS'); + + const { compiled, exception } = parser.result; + const headerName = compiled.slice(15, -1); + + // Tokenless is meaningful only for exception filters. + if ( headerName === '' && exception === false ) { return; } + + // Only exception filters are allowed to be global. + if ( parser.hasOptions() === false ) { + if ( exception ) { + writer.push([ 64, '', 1, compiled ]); + } + return; + } + + // https://github.com/gorhill/uBlock/issues/3375 + // Ignore instances of exception filter with negated hostnames, + // because there is no way to create an exception to an exception. + + for ( const { hn, not, bad } of parser.extOptions() ) { + if ( bad ) { continue; } + let kind = 0; + if ( exception ) { + if ( not ) { continue; } + kind |= 1; + } else if ( not ) { + kind |= 1; + } + writer.push([ 64, hn, kind, compiled ]); + } +}; + +httpheaderFilteringEngine.compileTemporary = function(parser) { + return { + session: sessionFilterDB, + selector: parser.result.compiled.slice(15, -1), + }; +}; + +// 01234567890123456789 +// responseheader(name) +// ^ ^ +// 15 -1 + +httpheaderFilteringEngine.fromCompiledContent = function(reader) { + reader.select('HTTPHEADER_FILTERS'); + + while ( reader.next() ) { + acceptedCount += 1; + const fingerprint = reader.fingerprint(); + if ( duplicates.has(fingerprint) ) { + discardedCount += 1; + continue; + } + duplicates.add(fingerprint); + const args = reader.args(); + if ( args.length < 4 ) { continue; } + filterDB.store(args[1], args[2], args[3].slice(15, -1)); + } +}; + +httpheaderFilteringEngine.getSession = function() { + return sessionFilterDB; +}; + +httpheaderFilteringEngine.apply = function(fctxt, headers) { + if ( filterDB.size === 0 ) { return; } + + const hostname = fctxt.getHostname(); + if ( hostname === '' ) { return; } + + const domain = fctxt.getDomain(); + let entity = entityFromDomain(domain); + if ( entity !== '' ) { + entity = `${hostname.slice(0, -domain.length)}${entity}`; + } else { + entity = '*'; + } + + $headers.clear(); + $exceptions.clear(); + + if ( sessionFilterDB.isNotEmpty ) { + sessionFilterDB.retrieve([ null, $exceptions ]); + } + filterDB.retrieve(hostname, [ $headers, $exceptions ]); + filterDB.retrieve(entity, [ $headers, $exceptions ], 1); + if ( $headers.size === 0 ) { return; } + + // https://github.com/gorhill/uBlock/issues/2835 + // Do not filter response headers if the site is under an `allow` rule. + if ( + µb.userSettings.advancedUserEnabled && + sessionFirewall.evaluateCellZY(hostname, hostname, '*') === 2 + ) { + return; + } + + const hasGlobalException = $exceptions.has(''); + + let modified = false; + + for ( const name of $headers ) { + for (;;) { + const i = headerIndexFromName(name, headers); + if ( i === -1 ) { break; } + const isExcepted = hasGlobalException || $exceptions.has(name); + if ( isExcepted ) { + if ( logger.enabled ) { + logOne(true, hasGlobalException ? '' : name, fctxt); + } + break; + } + headers.splice(i, 1); + if ( logger.enabled ) { + logOne(false, name, fctxt); + } + modified = true; + } + } + + return modified; +}; + +httpheaderFilteringEngine.toSelfie = function() { + return filterDB.toSelfie(); +}; + +httpheaderFilteringEngine.fromSelfie = function(selfie) { + filterDB.fromSelfie(selfie); +}; + +/******************************************************************************/ + +export default httpheaderFilteringEngine; + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/i18n.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/i18n.js new file mode 100644 index 0000000..f5b15ea --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/i18n.js @@ -0,0 +1,292 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +'use strict'; + +/******************************************************************************/ + +// This file should always be included at the end of the `body` tag, so as +// to ensure all i18n targets are already loaded. + +{ +// >>>>> start of local scope + +/******************************************************************************/ + +// https://github.com/gorhill/uBlock/issues/2084 +// Anything else than , , , , , and will +// be rendered as plain text. +// For , only href attribute must be present, and it MUST starts with +// `https://`, and includes no single- or double-quotes. +// No HTML entities are allowed, there is code to handle existing HTML +// entities already present in translation files until they are all gone. + +const allowedTags = new Set([ + 'a', + 'b', + 'code', + 'em', + 'i', + 'span', + 'u', +]); + +const expandHtmlEntities = (( ) => { + const entities = new Map([ + // TODO: Remove quote entities once no longer present in translation + // files. Other entities must stay. + [ '­', '\u00AD' ], + [ '“', '“' ], + [ '”', '”' ], + [ '‘', '‘' ], + [ '’', '’' ], + [ '<', '<' ], + [ '>', '>' ], + ]); + const decodeEntities = match => { + return entities.get(match) || match; + }; + return function(text) { + if ( text.indexOf('&') !== -1 ) { + text = text.replace(/&[a-z]+;/g, decodeEntities); + } + return text; + }; +})(); + +const safeTextToTextNode = function(text) { + return document.createTextNode(expandHtmlEntities(text)); +}; + +const sanitizeElement = function(node) { + if ( allowedTags.has(node.localName) === false ) { return null; } + node.removeAttribute('style'); + let child = node.firstElementChild; + while ( child !== null ) { + const next = child.nextElementSibling; + if ( sanitizeElement(child) === null ) { + child.remove(); + } + child = next; + } + return node; +}; + +const safeTextToDOM = function(text, parent) { + if ( text === '' ) { return; } + + // Fast path (most common). + if ( text.indexOf('<') === -1 ) { + const toInsert = safeTextToTextNode(text); + let toReplace = parent.childCount !== 0 + ? parent.firstChild + : null; + while ( toReplace !== null ) { + if ( toReplace.nodeType === 3 && toReplace.nodeValue === '_' ) { + break; + } + toReplace = toReplace.nextSibling; + } + if ( toReplace !== null ) { + parent.replaceChild(toInsert, toReplace); + } else { + parent.appendChild(toInsert); + } + return; + } + + // Slow path. + // `

` no longer allowed. Code below can be removed once all

's are + // gone from translation files. + text = text.replace(/^

|<\/p>/g, '') + .replace(/

/g, '\n\n'); + // Parse allowed HTML tags. + const domParser = new DOMParser(); + const parsedDoc = domParser.parseFromString(text, 'text/html'); + let node = parsedDoc.body.firstChild; + while ( node !== null ) { + const next = node.nextSibling; + switch ( node.nodeType ) { + case 1: // element + if ( sanitizeElement(node) === null ) { break; } + parent.appendChild(node); + break; + case 3: // text + parent.appendChild(node); + break; + default: + break; + } + node = next; + } +}; + +/******************************************************************************/ + +vAPI.i18n.safeTemplateToDOM = function(id, dict, parent) { + if ( parent === undefined ) { + parent = document.createDocumentFragment(); + } + let textin = vAPI.i18n(id); + if ( textin === '' ) { + return parent; + } + if ( textin.indexOf('{{') === -1 ) { + safeTextToDOM(textin, parent); + return parent; + } + const re = /\{\{\w+\}\}/g; + let textout = ''; + for (;;) { + let match = re.exec(textin); + if ( match === null ) { + textout += textin; + break; + } + textout += textin.slice(0, match.index); + let prop = match[0].slice(2, -2); + if ( dict.hasOwnProperty(prop) ) { + textout += dict[prop].replace(//g, '>'); + } else { + textout += prop; + } + textin = textin.slice(re.lastIndex); + } + safeTextToDOM(textout, parent); + return parent; +}; + +/******************************************************************************/ + +// Helper to deal with the i18n'ing of HTML files. +vAPI.i18n.render = function(context) { + const docu = document; + const root = context || docu; + + for ( const elem of root.querySelectorAll('[data-i18n]') ) { + let text = vAPI.i18n(elem.getAttribute('data-i18n')); + if ( !text ) { continue; } + if ( text.indexOf('{{') === -1 ) { + safeTextToDOM(text, elem); + continue; + } + // Handle selector-based placeholders: these placeholders tell where + // existing child DOM element are to be positioned relative to the + // localized text nodes. + const parts = text.split(/(\{\{[^}]+\}\})/); + const fragment = document.createDocumentFragment(); + let textBefore = ''; + for ( let part of parts ) { + if ( part === '' ) { continue; } + if ( part.startsWith('{{') && part.endsWith('}}') ) { + // TODO: remove detection of ':' once it no longer appears + // in translation files. + const pos = part.indexOf(':'); + if ( pos !== -1 ) { + part = part.slice(0, pos) + part.slice(-2); + } + const selector = part.slice(2, -2); + let node; + // Ideally, the i18n strings explicitly refer to the + // class of the element to insert. However for now we + // will create a class from what is currently found in + // the placeholder and first try to lookup the resulting + // selector. This way we don't have to revisit all + // translations just for the sake of declaring the proper + // selector in the placeholder field. + if ( selector.charCodeAt(0) !== 0x2E /* '.' */ ) { + node = elem.querySelector(`.${selector}`); + } + if ( node instanceof Element === false ) { + node = elem.querySelector(selector); + } + if ( node instanceof Element ) { + safeTextToDOM(textBefore, fragment); + fragment.appendChild(node); + textBefore = ''; + continue; + } + } + textBefore += part; + } + if ( textBefore !== '' ) { + safeTextToDOM(textBefore, fragment); + } + elem.appendChild(fragment); + } + + for ( const elem of root.querySelectorAll('[data-i18n-title]') ) { + const text = vAPI.i18n(elem.getAttribute('data-i18n-title')); + if ( !text ) { continue; } + elem.setAttribute('title', expandHtmlEntities(text)); + } + + for ( const elem of root.querySelectorAll('[placeholder]') ) { + elem.setAttribute( + 'placeholder', + vAPI.i18n(elem.getAttribute('placeholder')) + ); + } + + for ( const elem of root.querySelectorAll('[data-i18n-tip]') ) { + const text = vAPI.i18n(elem.getAttribute('data-i18n-tip')) + .replace(/
/g, '\n') + .replace(/\n{3,}/g, '\n\n'); + elem.setAttribute('data-tip', text); + if ( elem.getAttribute('aria-label') === 'data-tip' ) { + elem.setAttribute('aria-label', text); + } + } +}; + +vAPI.i18n.render(); + +/******************************************************************************/ + +vAPI.i18n.renderElapsedTimeToString = function(tstamp) { + let value = (Date.now() - tstamp) / 60000; + if ( value < 2 ) { + return vAPI.i18n('elapsedOneMinuteAgo'); + } + if ( value < 60 ) { + return vAPI.i18n('elapsedManyMinutesAgo').replace('{{value}}', Math.floor(value).toLocaleString()); + } + value /= 60; + if ( value < 2 ) { + return vAPI.i18n('elapsedOneHourAgo'); + } + if ( value < 24 ) { + return vAPI.i18n('elapsedManyHoursAgo').replace('{{value}}', Math.floor(value).toLocaleString()); + } + value /= 24; + if ( value < 2 ) { + return vAPI.i18n('elapsedOneDayAgo'); + } + return vAPI.i18n('elapsedManyDaysAgo').replace('{{value}}', Math.floor(value).toLocaleString()); +}; + +/******************************************************************************/ + +// <<<<< end of local scope +} + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/is-webrtc-supported.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/is-webrtc-supported.js new file mode 100644 index 0000000..5a3eea1 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/is-webrtc-supported.js @@ -0,0 +1,52 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015 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 +*/ + +// https://github.com/gorhill/uBlock/issues/533#issuecomment-164292868 +// If WebRTC is supported, there won't be an exception if we +// try to instanciate a peer connection object. + +// https://github.com/gorhill/uBlock/issues/533#issuecomment-168097594 +// Because Chromium leaks WebRTC connections after they have been closed +// and forgotten, we need to test for WebRTC support inside an iframe, this +// way the closed and forgottetn WebRTC connections are properly garbage +// collected. + +(function() { + 'use strict'; + + var pc = null; + try { + var PC = self.RTCPeerConnection || self.webkitRTCPeerConnection; + if ( PC ) { + pc = new PC(null); + } + } catch (ex) { + console.error(ex); + } + if ( pc !== null ) { + pc.close(); + } + + window.top.postMessage( + pc !== null ? 'webRTCSupported' : 'webRTCNotSupported', + window.location.origin + ); +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/logger-ui-inspector.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/logger-ui-inspector.js new file mode 100644 index 0000000..f474e49 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/logger-ui-inspector.js @@ -0,0 +1,679 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-2018 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 +*/ + +/* global uDom */ + +'use strict'; + +/******************************************************************************/ + +(( ) => { + +/******************************************************************************/ + +const showdomButton = uDom.nodeFromId('showdom'); + +// Don't bother if the browser is not modern enough. +if ( + typeof Map === 'undefined' || + Map.polyfill || + typeof WeakMap === 'undefined' +) { + showdomButton.classList.add('disabled'); + return; +} + +/******************************************************************************/ + +const logger = self.logger; +var inspectorConnectionId; +var inspectedTabId = 0; +var inspectedURL = ''; +var inspectedHostname = ''; +var inspector = uDom.nodeFromId('domInspector'); +var domTree = uDom.nodeFromId('domTree'); +var uidGenerator = 1; +var filterToIdMap = new Map(); + +/******************************************************************************/ + +const messaging = vAPI.messaging; + +vAPI.MessagingConnection.addListener(function(msg) { + if ( msg.from !== 'domInspector' || msg.to !== 'loggerUI' ) { return; } + switch ( msg.what ) { + case 'connectionBroken': + if ( inspectorConnectionId === msg.id ) { + filterToIdMap.clear(); + logger.removeAllChildren(domTree); + inspectorConnectionId = undefined; + } + injectInspector(); + break; + case 'connectionMessage': + if ( msg.payload.what === 'domLayoutFull' ) { + inspectedURL = msg.payload.url; + inspectedHostname = msg.payload.hostname; + renderDOMFull(msg.payload); + } else if ( msg.payload.what === 'domLayoutIncremental' ) { + renderDOMIncremental(msg.payload); + } + break; + case 'connectionRequested': + if ( msg.tabId === undefined || msg.tabId !== inspectedTabId ) { + return; + } + filterToIdMap.clear(); + logger.removeAllChildren(domTree); + inspectorConnectionId = msg.id; + return true; + } +}); + +/******************************************************************************/ + +const nodeFromDomEntry = function(entry) { + var node, value; + var li = document.createElement('li'); + li.setAttribute('id', entry.nid); + // expander/collapser + li.appendChild(document.createElement('span')); + // selector + node = document.createElement('code'); + node.textContent = entry.sel; + li.appendChild(node); + // descendant count + value = entry.cnt || 0; + node = document.createElement('span'); + node.textContent = value !== 0 ? value.toLocaleString() : ''; + node.setAttribute('data-cnt', value); + li.appendChild(node); + // cosmetic filter + if ( entry.filter === undefined ) { + return li; + } + node = document.createElement('code'); + node.classList.add('filter'); + value = filterToIdMap.get(entry.filter); + if ( value === undefined ) { + value = uidGenerator.toString(); + filterToIdMap.set(entry.filter, value); + uidGenerator += 1; + } + node.setAttribute('data-filter-id', value); + node.textContent = entry.filter; + li.appendChild(node); + li.classList.add('isCosmeticHide'); + return li; +}; + +/******************************************************************************/ + +const appendListItem = function(ul, li) { + ul.appendChild(li); + // Ancestor nodes of a node which is affected by a cosmetic filter will + // be marked as "containing cosmetic filters", for user convenience. + if ( li.classList.contains('isCosmeticHide') === false ) { return; } + for (;;) { + li = li.parentElement.parentElement; + if ( li === null ) { break; } + li.classList.add('hasCosmeticHide'); + } +}; + +/******************************************************************************/ + +const renderDOMFull = function(response) { + var domTreeParent = domTree.parentElement; + var ul = domTreeParent.removeChild(domTree); + logger.removeAllChildren(domTree); + + filterToIdMap.clear(); + + var lvl = 0; + var entries = response.layout; + var n = entries.length; + var li, entry; + for ( var i = 0; i < n; i++ ) { + entry = entries[i]; + if ( entry.lvl === lvl ) { + li = nodeFromDomEntry(entry); + appendListItem(ul, li); + continue; + } + if ( entry.lvl > lvl ) { + ul = document.createElement('ul'); + li.appendChild(ul); + li.classList.add('branch'); + li = nodeFromDomEntry(entry); + appendListItem(ul, li); + lvl = entry.lvl; + continue; + } + // entry.lvl < lvl + while ( entry.lvl < lvl ) { + ul = li.parentNode; + li = ul.parentNode; + ul = li.parentNode; + lvl -= 1; + } + li = nodeFromDomEntry(entry); + appendListItem(ul, li); + } + while ( ul.parentNode !== null ) { + ul = ul.parentNode; + } + ul.firstElementChild.classList.add('show'); + + domTreeParent.appendChild(domTree); +}; + +// https://www.youtube.com/watch?v=IDGNA83mxDo + +/******************************************************************************/ + +const patchIncremental = function(from, delta) { + var span, cnt; + var li = from.parentElement.parentElement; + var patchCosmeticHide = delta >= 0 && + from.classList.contains('isCosmeticHide') && + li.classList.contains('hasCosmeticHide') === false; + // Include descendants count when removing a node + if ( delta < 0 ) { + delta -= countFromNode(from); + } + for ( ; li.localName === 'li'; li = li.parentElement.parentElement ) { + span = li.children[2]; + if ( delta !== 0 ) { + cnt = countFromNode(li) + delta; + span.textContent = cnt !== 0 ? cnt.toLocaleString() : ''; + span.setAttribute('data-cnt', cnt); + } + if ( patchCosmeticHide ) { + li.classList.add('hasCosmeticHide'); + } + } +}; + +/******************************************************************************/ + +const renderDOMIncremental = function(response) { + // Process each journal entry: + // 1 = node added + // -1 = node removed + var journal = response.journal; + var nodes = new Map(response.nodes); + var entry, previous, li, ul; + for ( var i = 0, n = journal.length; i < n; i++ ) { + entry = journal[i]; + // Remove node + if ( entry.what === -1 ) { + li = document.getElementById(entry.nid); + if ( li === null ) { continue; } + patchIncremental(li, -1); + li.parentNode.removeChild(li); + continue; + } + // Modify node + if ( entry.what === 0 ) { + // TODO: update selector/filter + continue; + } + // Add node as sibling + if ( entry.what === 1 && entry.l ) { + previous = document.getElementById(entry.l); + // This should not happen + if ( previous === null ) { + // throw new Error('No left sibling!?'); + continue; + } + ul = previous.parentElement; + li = nodeFromDomEntry(nodes.get(entry.nid)); + ul.insertBefore(li, previous.nextElementSibling); + patchIncremental(li, 1); + continue; + } + // Add node as child + if ( entry.what === 1 && entry.u ) { + li = document.getElementById(entry.u); + // This should not happen + if ( li === null ) { + // throw new Error('No parent!?'); + continue; + } + ul = li.querySelector('ul'); + if ( ul === null ) { + ul = document.createElement('ul'); + li.appendChild(ul); + li.classList.add('branch'); + } + li = nodeFromDomEntry(nodes.get(entry.nid)); + ul.appendChild(li); + patchIncremental(li, 1); + continue; + } + } +}; + +// https://www.youtube.com/watch?v=6u2KPtJB9h8 + +/******************************************************************************/ + +const countFromNode = function(li) { + var span = li.children[2]; + var cnt = parseInt(span.getAttribute('data-cnt'), 10); + return isNaN(cnt) ? 0 : cnt; +}; + +/******************************************************************************/ + +const selectorFromNode = function(node) { + var selector = ''; + var code; + while ( node !== null ) { + if ( node.localName === 'li' ) { + code = node.querySelector('code'); + if ( code !== null ) { + selector = code.textContent + ' > ' + selector; + if ( selector.indexOf('#') !== -1 ) { + break; + } + } + } + node = node.parentElement; + } + return selector.slice(0, -3); +}; + +/******************************************************************************/ + +const selectorFromFilter = function(node) { + while ( node !== null ) { + if ( node.localName === 'li' ) { + var code = node.querySelector('code:nth-of-type(2)'); + if ( code !== null ) { + return code.textContent; + } + } + node = node.parentElement; + } + return ''; +}; + +/******************************************************************************/ + +const nidFromNode = function(node) { + var li = node; + while ( li !== null ) { + if ( li.localName === 'li' ) { + return li.id || ''; + } + li = li.parentElement; + } + return ''; +}; + +/******************************************************************************/ + +const startDialog = (function() { + let dialog; + let textarea; + let hideSelectors = []; + let unhideSelectors = []; + let inputTimer; + + const onInputChanged = (function() { + const parse = function() { + inputTimer = undefined; + hideSelectors = []; + unhideSelectors = []; + + const re = /^([^#]*)(#@?#)(.+)$/; + for ( let line of textarea.value.split(/\s*\n\s*/) ) { + line = line.trim(); + if ( line === '' || line.charAt(0) === '!' ) { continue; } + const matches = re.exec(line); + if ( matches === null || matches.length !== 4 ) { continue; } + if ( inspectedHostname.lastIndexOf(matches[1]) === -1 ) { + continue; + } + if ( matches[2] === '##' ) { + hideSelectors.push(matches[3]); + } else { + unhideSelectors.push(matches[3]); + } + } + + showCommitted(); + }; + + return function parseAsync() { + if ( inputTimer === undefined ) { + inputTimer = vAPI.setTimeout(parse, 743); + } + }; + })(); + + const onClicked = function(ev) { + var target = ev.target; + + ev.stopPropagation(); + + if ( target.id === 'createCosmeticFilters' ) { + messaging.send('loggerUI', { + what: 'createUserFilter', + filters: textarea.value, + }); + // Force a reload for the new cosmetic filter(s) to take effect + messaging.send('loggerUI', { + what: 'reloadTab', + tabId: inspectedTabId, + }); + return stop(); + } + }; + + const showCommitted = function() { + vAPI.MessagingConnection.sendTo(inspectorConnectionId, { + what: 'showCommitted', + hide: hideSelectors.join(',\n'), + unhide: unhideSelectors.join(',\n') + }); + }; + + const showInteractive = function() { + vAPI.MessagingConnection.sendTo(inspectorConnectionId, { + what: 'showInteractive', + hide: hideSelectors.join(',\n'), + unhide: unhideSelectors.join(',\n') + }); + }; + + const start = function() { + dialog = logger.modalDialog.create('#cosmeticFilteringDialog', stop); + textarea = dialog.querySelector('textarea'); + hideSelectors = []; + for ( const node of domTree.querySelectorAll('code.off') ) { + if ( node.classList.contains('filter') ) { continue; } + hideSelectors.push(selectorFromNode(node)); + } + const taValue = []; + for ( const selector of hideSelectors ) { + taValue.push(inspectedHostname + '##' + selector); + } + const ids = new Set(); + for ( const node of domTree.querySelectorAll('code.filter.off') ) { + const id = node.getAttribute('data-filter-id'); + if ( ids.has(id) ) { continue; } + ids.add(id); + unhideSelectors.push(node.textContent); + taValue.push(inspectedHostname + '#@#' + node.textContent); + } + textarea.value = taValue.join('\n'); + textarea.addEventListener('input', onInputChanged); + dialog.addEventListener('click', onClicked, true); + showCommitted(); + logger.modalDialog.show(); + }; + + const stop = function() { + if ( inputTimer !== undefined ) { + clearTimeout(inputTimer); + inputTimer = undefined; + } + showInteractive(); + textarea.removeEventListener('input', onInputChanged); + dialog.removeEventListener('click', onClicked, true); + dialog = undefined; + textarea = undefined; + hideSelectors = []; + unhideSelectors = []; + }; + + return start; +})(); + +/******************************************************************************/ + +const onClicked = function(ev) { + ev.stopPropagation(); + + if ( inspectedTabId === 0 ) { return; } + + var target = ev.target; + var parent = target.parentElement; + + // Expand/collapse branch + if ( + target.localName === 'span' && + parent instanceof HTMLLIElement && + parent.classList.contains('branch') && + target === parent.firstElementChild + ) { + var state = parent.classList.toggle('show'); + if ( !state ) { + for ( var node of parent.querySelectorAll('.branch') ) { + node.classList.remove('show'); + } + } + return; + } + + // Not a node or filter + if ( target.localName !== 'code' ) { return; } + + // Toggle cosmetic filter + if ( target.classList.contains('filter') ) { + vAPI.MessagingConnection.sendTo(inspectorConnectionId, { + what: 'toggleFilter', + original: false, + target: target.classList.toggle('off'), + selector: selectorFromNode(target), + filter: selectorFromFilter(target), + nid: nidFromNode(target) + }); + uDom('[data-filter-id="' + target.getAttribute('data-filter-id') + '"]', inspector).toggleClass( + 'off', + target.classList.contains('off') + ); + } + // Toggle node + else { + vAPI.MessagingConnection.sendTo(inspectorConnectionId, { + what: 'toggleNodes', + original: true, + target: target.classList.toggle('off') === false, + selector: selectorFromNode(target), + nid: nidFromNode(target) + }); + } + + var cantCreate = domTree.querySelector('.off') === null; + inspector.querySelector('.permatoolbar .revert').classList.toggle('disabled', cantCreate); + inspector.querySelector('.permatoolbar .commit').classList.toggle('disabled', cantCreate); +}; + +/******************************************************************************/ + +const onMouseOver = (function() { + var mouseoverTarget = null; + var mouseoverTimer = null; + + var timerHandler = function() { + mouseoverTimer = null; + vAPI.MessagingConnection.sendTo(inspectorConnectionId, { + what: 'highlightOne', + selector: selectorFromNode(mouseoverTarget), + nid: nidFromNode(mouseoverTarget), + scrollTo: true + }); + }; + + return function(ev) { + if ( inspectedTabId === 0 ) { return; } + // Convenience: skip real-time highlighting if shift key is pressed. + if ( ev.shiftKey ) { return; } + // Find closest `li` + var target = ev.target; + while ( target !== null ) { + if ( target.localName === 'li' ) { break; } + target = target.parentElement; + } + if ( target === mouseoverTarget ) { return; } + mouseoverTarget = target; + if ( mouseoverTimer === null ) { + mouseoverTimer = vAPI.setTimeout(timerHandler, 50); + } + }; +})(); + +/******************************************************************************/ + +const currentTabId = function() { + if ( showdomButton.classList.contains('active') === false ) { return 0; } + return logger.tabIdFromPageSelector(); +}; + +/******************************************************************************/ + +const injectInspector = function() { + const tabId = currentTabId(); + if ( tabId <= 0 ) { return; } + inspectedTabId = tabId; + messaging.send('loggerUI', { + what: 'scriptlet', + tabId, + scriptlet: 'dom-inspector', + }); +}; + +/******************************************************************************/ + +const shutdownInspector = function() { + if ( inspectorConnectionId !== undefined ) { + vAPI.MessagingConnection.disconnectFrom(inspectorConnectionId); + inspectorConnectionId = undefined; + } + logger.removeAllChildren(domTree); + inspector.classList.remove('vExpanded'); + inspectedTabId = 0; +}; + +/******************************************************************************/ + +const onTabIdChanged = function() { + const tabId = currentTabId(); + if ( tabId <= 0 ) { + return toggleOff(); + } + if ( inspectedTabId !== tabId ) { + shutdownInspector(); + injectInspector(); + } +}; + +/******************************************************************************/ + +const toggleVCompactView = function() { + var state = inspector.classList.toggle('vExpanded'); + var branches = document.querySelectorAll('#domInspector li.branch'); + for ( var branch of branches ) { + branch.classList.toggle('show', state); + } +}; + +const toggleHCompactView = function() { + inspector.classList.toggle('hCompact'); +}; + +/******************************************************************************/ +/* +var toggleHighlightMode = function() { + vAPI.MessagingConnection.sendTo(inspectorConnectionId, { + what: 'highlightMode', + invert: uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').classList.toggle('invert') + }); +}; +*/ +/******************************************************************************/ + +const revert = function() { + uDom('#domTree .off').removeClass('off'); + vAPI.MessagingConnection.sendTo( + inspectorConnectionId, + { what: 'resetToggledNodes' } + ); + inspector.querySelector('.permatoolbar .revert').classList.add('disabled'); + inspector.querySelector('.permatoolbar .commit').classList.add('disabled'); +}; + +/******************************************************************************/ + +const toggleOn = function() { + uDom.nodeFromId('inspectors').classList.add('dom'); + window.addEventListener('beforeunload', toggleOff); + document.addEventListener('tabIdChanged', onTabIdChanged); + domTree.addEventListener('click', onClicked, true); + domTree.addEventListener('mouseover', onMouseOver, true); + uDom.nodeFromSelector('#domInspector .vCompactToggler').addEventListener('click', toggleVCompactView); + uDom.nodeFromSelector('#domInspector .hCompactToggler').addEventListener('click', toggleHCompactView); + //uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').addEventListener('click', toggleHighlightMode); + uDom.nodeFromSelector('#domInspector .permatoolbar .revert').addEventListener('click', revert); + uDom.nodeFromSelector('#domInspector .permatoolbar .commit').addEventListener('click', startDialog); + injectInspector(); +}; + +/******************************************************************************/ + +const toggleOff = function() { + showdomButton.classList.remove('active'); + uDom.nodeFromId('inspectors').classList.remove('dom'); + shutdownInspector(); + window.removeEventListener('beforeunload', toggleOff); + document.removeEventListener('tabIdChanged', onTabIdChanged); + domTree.removeEventListener('click', onClicked, true); + domTree.removeEventListener('mouseover', onMouseOver, true); + uDom.nodeFromSelector('#domInspector .vCompactToggler').removeEventListener('click', toggleVCompactView); + uDom.nodeFromSelector('#domInspector .hCompactToggler').removeEventListener('click', toggleHCompactView); + //uDom.nodeFromSelector('#domInspector .permatoolbar .highlightMode').removeEventListener('click', toggleHighlightMode); + uDom.nodeFromSelector('#domInspector .permatoolbar .revert').removeEventListener('click', revert); + uDom.nodeFromSelector('#domInspector .permatoolbar .commit').removeEventListener('click', startDialog); + inspectedTabId = 0; +}; + +/******************************************************************************/ + +const toggle = function() { + if ( showdomButton.classList.toggle('active') ) { + toggleOn(); + } else { + toggleOff(); + } + logger.resize(); +}; + +/******************************************************************************/ + +showdomButton.addEventListener('click', toggle); + +/******************************************************************************/ + +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/logger-ui.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/logger-ui.js new file mode 100644 index 0000000..c67e2fd --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/logger-ui.js @@ -0,0 +1,2893 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-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 +*/ + +/* global uDom */ + +'use strict'; + +/******************************************************************************/ + +import { hostnameFromURI } from './uri-utils.js'; + +/******************************************************************************/ + +// TODO: fix the inconsistencies re. realm vs. filter source which have +// accumulated over time. + +const messaging = vAPI.messaging; +const logger = self.logger = { ownerId: Date.now() }; +const logDate = new Date(); +const logDateTimezoneOffset = logDate.getTimezoneOffset() * 60000; +const loggerEntries = []; + +let filteredLoggerEntries = []; +let filteredLoggerEntryVoidedCount = 0; + +let popupLoggerBox; +let popupLoggerTooltips; +let activeTabId = 0; +let filterAuthorMode = false; +let selectedTabId = 0; +let netInspectorPaused = false; +let cnameOfEnabled = false; + +/******************************************************************************/ + +// Various helpers. + +const tabIdFromPageSelector = logger.tabIdFromPageSelector = function() { + const value = uDom.nodeFromId('pageSelector').value; + return value !== '_' ? (parseInt(value, 10) || 0) : activeTabId; +}; + +const tabIdFromAttribute = function(elem) { + const value = elem.getAttribute('data-tabid') || ''; + const tabId = parseInt(value, 10); + return isNaN(tabId) ? 0 : tabId; +}; + +/******************************************************************************/ +/******************************************************************************/ + +// Current design allows for only one modal DOM-based dialog at any given time. +// +const modalDialog = (( ) => { + const overlay = uDom.nodeFromId('modalOverlay'); + const container = overlay.querySelector( + ':scope > div > div:nth-of-type(1)' + ); + const closeButton = overlay.querySelector( + ':scope > div > div:nth-of-type(2)' + ); + let onDestroyed; + + const removeChildren = logger.removeAllChildren = function(node) { + while ( node.firstChild ) { + node.removeChild(node.firstChild); + } + }; + + const create = function(selector, destroyListener) { + const template = document.querySelector(selector); + const dialog = template.cloneNode(true); + removeChildren(container); + container.appendChild(dialog); + onDestroyed = destroyListener; + return dialog; + }; + + const show = function() { + overlay.classList.add('on'); + }; + + const destroy = function() { + overlay.classList.remove('on'); + const dialog = container.firstElementChild; + removeChildren(container); + if ( typeof onDestroyed === 'function' ) { + onDestroyed(dialog); + } + onDestroyed = undefined; + }; + + const onClose = function(ev) { + if ( ev.target === overlay || ev.target === closeButton ) { + destroy(); + } + }; + overlay.addEventListener('click', onClose); + closeButton.addEventListener('click', onClose); + + return { create, show, destroy }; +})(); + +self.logger.modalDialog = modalDialog; + +/******************************************************************************/ +/******************************************************************************/ + +const prettyRequestTypes = { + 'main_frame': 'doc', + 'stylesheet': 'css', + 'sub_frame': 'frame', + 'xmlhttprequest': 'xhr' +}; + +const uglyRequestTypes = { + 'doc': 'main_frame', + 'css': 'stylesheet', + 'frame': 'sub_frame', + 'xhr': 'xmlhttprequest' +}; + +let allTabIds = new Map(); +let allTabIdsToken; + +/******************************************************************************/ +/******************************************************************************/ + +const regexFromURLFilteringResult = function(result) { + const beg = result.indexOf(' '); + const end = result.indexOf(' ', beg + 1); + const url = result.slice(beg + 1, end); + if ( url === '*' ) { + return new RegExp('^.*$', 'gi'); + } + return new RegExp('^' + url.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'); +}; + +/******************************************************************************/ + +// Emphasize hostname in URL, as this is what matters in uMatrix's rules. + +const nodeFromURL = function(parent, url, re) { + const fragment = document.createDocumentFragment(); + if ( re === undefined ) { + fragment.textContent = url; + } else { + if ( typeof re === 'string' ) { + re = new RegExp(re.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'); + } + const matches = re.exec(url); + if ( matches === null || matches[0].length === 0 ) { + fragment.textContent = url; + } else { + if ( matches.index !== 0 ) { + fragment.appendChild( + document.createTextNode(url.slice(0, matches.index)) + ); + } + const b = document.createElement('b'); + b.textContent = url.slice(matches.index, re.lastIndex); + fragment.appendChild(b); + if ( re.lastIndex !== url.length ) { + fragment.appendChild( + document.createTextNode(url.slice(re.lastIndex)) + ); + } + } + } + if ( /^https?:\/\//.test(url) ) { + const a = document.createElement('a'); + a.setAttribute('href', url); + a.setAttribute('target', '_blank'); + fragment.appendChild(a); + } + parent.appendChild(fragment); +}; + +/******************************************************************************/ + +const padTo2 = function(v) { + return v < 10 ? '0' + v : v; +}; + +const normalizeToStr = function(s) { + return typeof s === 'string' && s !== '' ? s : ''; +}; + +/******************************************************************************/ + +const LogEntry = function(details) { + if ( details instanceof Object === false ) { return; } + const receiver = LogEntry.prototype; + for ( const prop in receiver ) { + if ( + details.hasOwnProperty(prop) && + details[prop] !== receiver[prop] + ) { + this[prop] = details[prop]; + } + } + this.type = details.stype || details.type; + if ( details.aliasURL !== undefined ) { + this.aliased = true; + } + if ( this.tabDomain === '' ) { + this.tabDomain = this.tabHostname || ''; + } + if ( this.docDomain === '' ) { + this.docDomain = this.docHostname || ''; + } + if ( this.domain === '' ) { + this.domain = details.hostname || ''; + } +}; +LogEntry.prototype = { + aliased: false, + dead: false, + docDomain: '', + docHostname: '', + domain: '', + filter: undefined, + id: '', + realm: '', + tabDomain: '', + tabHostname: '', + tabId: undefined, + textContent: '', + tstamp: 0, + type: '', + voided: false, +}; + +/******************************************************************************/ + +const createLogSeparator = function(details, text) { + const separator = new LogEntry(); + separator.tstamp = details.tstamp; + separator.realm = 'message'; + separator.tabId = details.tabId; + separator.type = 'tabLoad'; + separator.textContent = ''; + + const textContent = []; + logDate.setTime(separator.tstamp - logDateTimezoneOffset); + textContent.push( + // cell 0 + padTo2(logDate.getUTCHours()) + ':' + + padTo2(logDate.getUTCMinutes()) + ':' + + padTo2(logDate.getSeconds()), + // cell 1 + text + ); + separator.textContent = textContent.join('\t'); + + if ( details.voided ) { + separator.voided = true; + } + + return separator; +}; + +/******************************************************************************/ + +// TODO: once refactoring is mature, consider using push() instead of +// unshift(). This will require inverting the access logic +// throughout the code. +// +const processLoggerEntries = function(response) { + const entries = response.entries; + if ( entries.length === 0 ) { return; } + + const autoDeleteVoidedRows = uDom.nodeFromId('pageSelector').value === '_'; + const previousCount = filteredLoggerEntries.length; + + for ( const entry of entries ) { + const unboxed = JSON.parse(entry); + if ( unboxed.filter instanceof Object ){ + loggerStats.processFilter(unboxed.filter); + } + if ( netInspectorPaused ) { continue; } + const parsed = parseLogEntry(unboxed); + if ( + parsed.tabId !== undefined && + allTabIds.has(parsed.tabId) === false + ) { + if ( autoDeleteVoidedRows ) { continue; } + parsed.voided = true; + } + if ( + parsed.type === 'main_frame' && + parsed.aliased === false && ( + parsed.filter === undefined || + parsed.filter.source !== 'redirect' + ) + ) { + const separator = createLogSeparator(parsed, unboxed.url); + loggerEntries.unshift(separator); + if ( rowFilterer.filterOne(separator) ) { + filteredLoggerEntries.unshift(separator); + if ( separator.voided ) { + filteredLoggerEntryVoidedCount += 1; + } + } + } + if ( cnameOfEnabled === false && parsed.aliased ) { + uDom.nodeFromId('filterExprCnameOf').style.display = ''; + cnameOfEnabled = true; + } + loggerEntries.unshift(parsed); + if ( rowFilterer.filterOne(parsed) ) { + filteredLoggerEntries.unshift(parsed); + if ( parsed.voided ) { + filteredLoggerEntryVoidedCount += 1; + } + } + } + + const addedCount = filteredLoggerEntries.length - previousCount; + if ( addedCount !== 0 ) { + viewPort.updateContent(addedCount); + rowJanitor.inserted(addedCount); + } +}; + +/******************************************************************************/ + +const parseLogEntry = function(details) { + // Patch realm until changed all over codebase to make this unnecessary + if ( details.realm === 'cosmetic' ) { + details.realm = 'extended'; + } + + const entry = new LogEntry(details); + + // Assemble the text content, i.e. the pre-built string which will be + // used to match logger output filtering expressions. + const textContent = []; + + // Cell 0 + logDate.setTime(details.tstamp - logDateTimezoneOffset); + textContent.push( + padTo2(logDate.getUTCHours()) + ':' + + padTo2(logDate.getUTCMinutes()) + ':' + + padTo2(logDate.getSeconds()) + ); + + // Cell 1 + if ( details.realm === 'message' ) { + textContent.push(details.text); + entry.textContent = textContent.join('\t'); + return entry; + } + + // Cell 1, 2 + if ( entry.filter !== undefined ) { + textContent.push(entry.filter.raw); + if ( entry.filter.result === 1 ) { + textContent.push('--'); + } else if ( entry.filter.result === 2 ) { + textContent.push('++'); + } else if ( entry.filter.result === 3 ) { + textContent.push('**'); + } else if ( entry.filter.source === 'redirect' ) { + textContent.push('<<'); + } else { + textContent.push(''); + } + } else { + textContent.push('', ''); + } + + // Cell 3 + textContent.push(normalizeToStr(entry.docHostname)); + + // Cell 4: partyness + if ( + entry.realm === 'network' && + typeof entry.domain === 'string' && + entry.domain !== '' + ) { + let partyness = ''; + if ( entry.tabDomain !== undefined ) { + if ( entry.tabId < 0 ) { + partyness += '0,'; + } + partyness += entry.domain === entry.tabDomain ? '1' : '3'; + } else { + partyness += '?'; + } + if ( entry.docDomain !== entry.tabDomain ) { + partyness += ','; + if ( entry.docDomain !== undefined ) { + partyness += entry.domain === entry.docDomain ? '1' : '3'; + } else { + partyness += '?'; + } + } + textContent.push(partyness); + } else { + textContent.push(''); + } + + // Cell 5 + textContent.push( + normalizeToStr(prettyRequestTypes[entry.type] || entry.type) + ); + + // Cell 6 + textContent.push(normalizeToStr(details.url)); + + // Hidden cells -- useful for row-filtering purpose + + // Cell 7 + if ( entry.aliased ) { + textContent.push(`aliasURL=${details.aliasURL}`); + } + + entry.textContent = textContent.join('\t'); + return entry; +}; + +/******************************************************************************/ + +const viewPort = (( ) => { + const vwRenderer = document.getElementById('vwRenderer'); + const vwScroller = document.getElementById('vwScroller'); + const vwVirtualContent = document.getElementById('vwVirtualContent'); + const vwContent = document.getElementById('vwContent'); + const vwLineSizer = document.getElementById('vwLineSizer'); + const vwLogEntryTemplate = document.querySelector('#logEntryTemplate > div'); + const vwEntries = []; + + const detailableRealms = new Set([ 'network', 'extended' ]); + + let vwHeight = 0; + let lineHeight = 0; + let wholeHeight = 0; + let lastTopPix = 0; + let lastTopRow = 0; + let scrollTimer; + let resizeTimer; + + const ViewEntry = function() { + this.div = document.createElement('div'); + this.div.className = 'logEntry'; + vwContent.appendChild(this.div); + this.logEntry = undefined; + }; + ViewEntry.prototype = { + dispose: function() { + vwContent.removeChild(this.div); + }, + }; + + const rowFromScrollTopPix = function(px) { + return lineHeight !== 0 ? Math.floor(px / lineHeight) : 0; + }; + + // This is called when the browser fired scroll events + const onScrollChanged = function() { + const newScrollTopPix = vwScroller.scrollTop; + const delta = newScrollTopPix - lastTopPix; + if ( delta === 0 ) { return; } + lastTopPix = newScrollTopPix; + if ( filteredLoggerEntries.length <= 2 ) { return; } + // No entries were rolled = all entries keep their current details + if ( rollLines(rowFromScrollTopPix(newScrollTopPix)) ) { + fillLines(); + } + positionLines(); + vwContent.style.top = `${lastTopPix}px`; + }; + + // Coalesce scroll events + const onScroll = function() { + if ( scrollTimer !== undefined ) { return; } + scrollTimer = setTimeout( + ( ) => { + scrollTimer = requestAnimationFrame(( ) => { + scrollTimer = undefined; + onScrollChanged(); + }); + }, + 1000/32 + ); + }; + + vwScroller.addEventListener('scroll', onScroll, { passive: true }); + + const onLayoutChanged = function() { + vwHeight = vwRenderer.clientHeight; + vwContent.style.height = `${vwScroller.clientHeight}px`; + + const vExpanded = + uDom.nodeFromSelector('#netInspector .vCompactToggler') + .classList + .contains('vExpanded'); + + let newLineHeight = + vwLineSizer.querySelector('.oneLine').clientHeight; + + if ( vExpanded ) { + newLineHeight *= loggerSettings.linesPerEntry; + } + + const lineCount = newLineHeight !== 0 + ? Math.ceil(vwHeight / newLineHeight) + 1 + : 0; + if ( lineCount > vwEntries.length ) { + do { + vwEntries.push(new ViewEntry()); + } while ( lineCount > vwEntries.length ); + } else if ( lineCount < vwEntries.length ) { + do { + vwEntries.pop().dispose(); + } while ( lineCount < vwEntries.length ); + } + + const cellWidths = Array.from( + vwLineSizer.querySelectorAll('.oneLine span') + ).map((el, i) => { + return loggerSettings.columns[i] !== false + ? el.clientWidth + 1 + : 0; + }); + const reservedWidth = + cellWidths[0] + cellWidths[2] + cellWidths[4] + cellWidths[5]; + cellWidths[6] = 0.5; + if ( cellWidths[1] === 0 && cellWidths[3] === 0 ) { + cellWidths[6] = 1; + } else if ( cellWidths[1] === 0 ) { + cellWidths[3] = 0.35; + cellWidths[6] = 0.65; + } else if ( cellWidths[3] === 0 ) { + cellWidths[1] = 0.35; + cellWidths[6] = 0.65; + } else { + cellWidths[1] = 0.25; + cellWidths[3] = 0.25; + cellWidths[6] = 0.5; + } + const style = document.getElementById('vwRendererRuntimeStyles'); + const cssRules = [ + '#vwContent .logEntry {', + ` height: ${newLineHeight}px;`, + '}', + '#vwContent .logEntry > div > span:nth-of-type(1) {', + ` width: ${cellWidths[0]}px;`, + '}', + '#vwContent .logEntry > div > span:nth-of-type(2) {', + ` width: calc(calc(100% - ${reservedWidth}px) * ${cellWidths[1]});`, + '}', + '#vwContent .logEntry > div.messageRealm > span:nth-of-type(2) {', + ` width: calc(100% - ${cellWidths[0]}px);`, + '}', + '#vwContent .logEntry > div > span:nth-of-type(3) {', + ` width: ${cellWidths[2]}px;`, + '}', + '#vwContent .logEntry > div > span:nth-of-type(4) {', + ` width: calc(calc(100% - ${reservedWidth}px) * ${cellWidths[3]});`, + '}', + '#vwContent .logEntry > div > span:nth-of-type(5) {', + ` width: ${cellWidths[4]}px;`, + '}', + '#vwContent .logEntry > div > span:nth-of-type(6) {', + ` width: ${cellWidths[5]}px;`, + '}', + '#vwContent .logEntry > div > span:nth-of-type(7) {', + ` width: calc(calc(100% - ${reservedWidth}px) * ${cellWidths[6]});`, + '}', + '', + ]; + for ( let i = 0; i < cellWidths.length; i++ ) { + if ( cellWidths[i] !== 0 ) { continue; } + cssRules.push( + `#vwContent .logEntry > div > span:nth-of-type(${i + 1}) {`, + ' display: none;', + '}' + ); + } + style.textContent = cssRules.join('\n'); + + lineHeight = newLineHeight; + positionLines(); + uDom.nodeFromId('netInspector') + .classList + .toggle('vExpanded', vExpanded); + + updateContent(0); + }; + + const updateLayout = function() { + if ( resizeTimer !== undefined ) { return; } + resizeTimer = setTimeout( + ( ) => { + resizeTimer = requestAnimationFrame(( ) => { + resizeTimer = undefined; + onLayoutChanged(); + }); + }, + 1000/8 + ); + }; + + window.addEventListener('resize', updateLayout, { passive: true }); + + updateLayout(); + + const renderFilterToSpan = function(span, filter) { + if ( filter.charCodeAt(0) !== 0x23 /* '#' */ ) { return false; } + const match = /^#@?#/.exec(filter); + if ( match === null ) { return false; } + let child = document.createElement('span'); + child.textContent = match[0]; + span.appendChild(child); + child = document.createElement('span'); + child.textContent = filter.slice(match[0].length); + span.appendChild(child); + return true; + }; + + const renderToDiv = function(vwEntry, i) { + if ( i >= filteredLoggerEntries.length ) { + vwEntry.logEntry = undefined; + return null; + } + + const details = filteredLoggerEntries[i]; + if ( vwEntry.logEntry === details ) { + return vwEntry.div.firstElementChild; + } + + vwEntry.logEntry = details; + + const cells = details.textContent.split('\t'); + const div = vwLogEntryTemplate.cloneNode(true); + const divcl = div.classList; + let span; + + // Realm + if ( details.realm !== undefined ) { + divcl.add(details.realm + 'Realm'); + } + + // Timestamp + span = div.children[0]; + span.textContent = cells[0]; + + // Tab id + if ( details.tabId !== undefined ) { + div.setAttribute('data-tabid', details.tabId); + if ( details.voided ) { + divcl.add('voided'); + } + } + + if ( details.realm === 'message' ) { + if ( details.type !== undefined ) { + div.setAttribute('data-type', details.type); + } + span = div.children[1]; + span.textContent = cells[1]; + return div; + } + + if ( detailableRealms.has(details.realm) ) { + divcl.add('canDetails'); + } + + // Filter + const filter = details.filter || undefined; + let filteringType; + if ( filter !== undefined ) { + if ( typeof filter.source === 'string' ) { + filteringType = filter.source; + } + if ( filteringType === 'static' ) { + divcl.add('canLookup'); + } else if ( details.realm === 'extended' ) { + divcl.toggle('canLookup', /^#@?#/.test(filter.raw)); + divcl.toggle('isException', filter.raw.startsWith('#@#')); + } + if ( filter.modifier === true ) { + div.setAttribute('data-modifier', ''); + } + } + span = div.children[1]; + if ( renderFilterToSpan(span, cells[1]) === false ) { + span.textContent = cells[1]; + } + + // Event + if ( cells[2] === '--' ) { + div.setAttribute('data-status', '1'); + } else if ( cells[2] === '++' ) { + div.setAttribute('data-status', '2'); + } else if ( cells[2] === '**' ) { + div.setAttribute('data-status', '3'); + } else if ( cells[2] === '<<' ) { + divcl.add('redirect'); + } + span = div.children[2]; + span.textContent = cells[2]; + + // Origins + if ( details.tabHostname ) { + div.setAttribute('data-tabhn', details.tabHostname); + } + if ( details.docHostname ) { + div.setAttribute('data-dochn', details.docHostname); + } + span = div.children[3]; + span.textContent = cells[3]; + + // Partyness + if ( + cells[4] !== '' && + details.realm === 'network' && + details.domain !== undefined + ) { + let text = `${details.tabDomain}`; + if ( details.docDomain !== details.tabDomain ) { + text += ` \u22ef ${details.docDomain}`; + } + text += ` \u21d2 ${details.domain}`; + div.setAttribute('data-parties', text); + } + span = div.children[4]; + span.textContent = cells[4]; + + // Type + span = div.children[5]; + span.textContent = cells[5]; + + // URL + let re; + if ( filteringType === 'static' ) { + re = new RegExp(filter.regex, 'gi'); + } else if ( filteringType === 'dynamicUrl' ) { + re = regexFromURLFilteringResult(filter.rule.join(' ')); + } + nodeFromURL(div.children[6], cells[6], re); + + // Alias URL (CNAME, etc.) + if ( cells.length > 7 ) { + const pos = details.textContent.lastIndexOf('\taliasURL='); + if ( pos !== -1 ) { + div.setAttribute('data-aliasid', details.id); + } + } + + return div; + }; + + // The idea is that positioning DOM elements is faster than + // removing/inserting DOM elements. + const positionLines = function() { + if ( lineHeight === 0 ) { return; } + let y = -(lastTopPix % lineHeight); + for ( const vwEntry of vwEntries ) { + vwEntry.div.style.top = `${y}px`; + y += lineHeight; + } + }; + + const rollLines = function(topRow) { + let delta = topRow - lastTopRow; + let deltaLength = Math.abs(delta); + // No point rolling if no rows can be reused + if ( deltaLength > 0 && deltaLength < vwEntries.length ) { + if ( delta < 0 ) { // Move bottom rows to the top + vwEntries.unshift(...vwEntries.splice(delta)); + } else { // Move top rows to the bottom + vwEntries.push(...vwEntries.splice(0, delta)); + } + } + lastTopRow = topRow; + return delta; + }; + + const fillLines = function() { + let rowBeg = lastTopRow; + for ( const vwEntry of vwEntries ) { + const newDiv = renderToDiv(vwEntry, rowBeg); + const container = vwEntry.div; + const oldDiv = container.firstElementChild; + if ( newDiv !== null ) { + if ( oldDiv === null ) { + container.appendChild(newDiv); + } else if ( newDiv !== oldDiv ) { + container.removeChild(oldDiv); + container.appendChild(newDiv); + } + } else if ( oldDiv !== null ) { + container.removeChild(oldDiv); + } + rowBeg += 1; + } + }; + + const contentChanged = function(addedCount) { + lastTopRow += addedCount; + const newWholeHeight = Math.max( + filteredLoggerEntries.length * lineHeight, + vwRenderer.clientHeight + ); + if ( newWholeHeight !== wholeHeight ) { + vwVirtualContent.style.height = `${newWholeHeight}px`; + wholeHeight = newWholeHeight; + } + }; + + const updateContent = function(addedCount) { + contentChanged(addedCount); + // Content changed + if ( addedCount === 0 ) { + if ( + lastTopRow !== 0 && + lastTopRow + vwEntries.length > filteredLoggerEntries.length + ) { + lastTopRow = filteredLoggerEntries.length - vwEntries.length; + if ( lastTopRow < 0 ) { lastTopRow = 0; } + lastTopPix = lastTopRow * lineHeight; + vwContent.style.top = `${lastTopPix}px`; + vwScroller.scrollTop = lastTopPix; + positionLines(); + } + fillLines(); + return; + } + + // Content added + // Preserve scroll position + if ( lastTopPix === 0 ) { + rollLines(0); + positionLines(); + fillLines(); + return; + } + + // Preserve row position + lastTopPix += lineHeight * addedCount; + vwContent.style.top = `${lastTopPix}px`; + vwScroller.scrollTop = lastTopPix; + }; + + return { updateContent, updateLayout, }; +})(); + +/******************************************************************************/ + +const updateCurrentTabTitle = (( ) => { + const i18nCurrentTab = vAPI.i18n('loggerCurrentTab'); + + return function() { + const select = uDom.nodeFromId('pageSelector'); + if ( select.value !== '_' || activeTabId === 0 ) { return; } + const opt0 = select.querySelector('[value="_"]'); + const opt1 = select.querySelector(`[value="${activeTabId}"]`); + let text = i18nCurrentTab; + if ( opt1 !== null ) { + text += ' / ' + opt1.textContent; + } + opt0.textContent = text; + }; +})(); + +/******************************************************************************/ + +const synchronizeTabIds = function(newTabIds) { + const select = uDom.nodeFromId('pageSelector'); + const selectedTabValue = select.value; + const oldTabIds = allTabIds; + + // Collate removed tab ids. + const toVoid = new Set(); + for ( const tabId of oldTabIds.keys() ) { + if ( newTabIds.has(tabId) ) { continue; } + toVoid.add(tabId); + } + allTabIds = newTabIds; + + // Mark as "void" all logger entries which are linked to now invalid + // tab ids. + // When an entry is voided without being removed, we re-create a new entry + // in order to ensure the entry has a new identity. A new identify ensures + // that identity-based associations elsewhere are automatically + // invalidated. + if ( toVoid.size !== 0 ) { + const autoDeleteVoidedRows = selectedTabValue === '_'; + let rowVoided = false; + for ( let i = 0, n = loggerEntries.length; i < n; i++ ) { + const entry = loggerEntries[i]; + if ( toVoid.has(entry.tabId) === false ) { continue; } + if ( entry.voided ) { continue; } + rowVoided = entry.voided = true; + if ( autoDeleteVoidedRows ) { + entry.dead = true; + } + loggerEntries[i] = new LogEntry(entry); + } + if ( rowVoided ) { + rowFilterer.filterAll(); + } + } + + // Remove popup if it is currently bound to a removed tab. + if ( toVoid.has(popupManager.tabId) ) { + popupManager.toggleOff(); + } + + const tabIds = Array.from(newTabIds.keys()).sort(function(a, b) { + return newTabIds.get(a).localeCompare(newTabIds.get(b)); + }); + let j = 3; + for ( let i = 0; i < tabIds.length; i++ ) { + const tabId = tabIds[i]; + if ( tabId <= 0 ) { continue; } + if ( j === select.options.length ) { + select.appendChild(document.createElement('option')); + } + const option = select.options[j]; + // Truncate too long labels. + option.textContent = newTabIds.get(tabId).slice(0, 80); + option.setAttribute('value', tabId); + if ( option.value === selectedTabValue ) { + select.selectedIndex = j; + option.setAttribute('selected', ''); + } else { + option.removeAttribute('selected'); + } + j += 1; + } + while ( j < select.options.length ) { + select.removeChild(select.options[j]); + } + if ( select.value !== selectedTabValue ) { + select.selectedIndex = 0; + select.value = ''; + select.options[0].setAttribute('selected', ''); + pageSelectorChanged(); + } + + updateCurrentTabTitle(); +}; + +/******************************************************************************/ + +const onLogBufferRead = function(response) { + if ( !response || response.unavailable ) { return; } + + // Disable tooltips? + if ( + popupLoggerTooltips === undefined && + response.tooltips !== undefined + ) { + popupLoggerTooltips = response.tooltips; + if ( popupLoggerTooltips === false ) { + uDom('[data-i18n-title]').attr('title', ''); + } + } + + // Tab id of currently active tab + let activeTabIdChanged = false; + if ( response.activeTabId ) { + activeTabIdChanged = response.activeTabId !== activeTabId; + activeTabId = response.activeTabId; + } + + if ( Array.isArray(response.tabIds) ) { + response.tabIds = new Map(response.tabIds); + } + + // List of tab ids has changed + if ( response.tabIds !== undefined ) { + synchronizeTabIds(response.tabIds); + allTabIdsToken = response.tabIdsToken; + } + + filterAuthorMode = response.filterAuthorMode === true; + + if ( activeTabIdChanged ) { + pageSelectorFromURLHash(); + } + + processLoggerEntries(response); + + // Synchronize DOM with sent logger data + document.documentElement.classList.toggle( + 'colorBlind', + response.colorBlind === true + ); + uDom.nodeFromId('clean').classList.toggle( + 'disabled', + filteredLoggerEntryVoidedCount === 0 + ); + uDom.nodeFromId('clear').classList.toggle( + 'disabled', + filteredLoggerEntries.length === 0 + ); +}; + +/******************************************************************************/ + +const readLogBuffer = (( ) => { + let timer; + + const readLogBufferNow = async function() { + if ( logger.ownerId === undefined ) { return; } + + const msg = { + what: 'readAll', + ownerId: logger.ownerId, + tabIdsToken: allTabIdsToken, + }; + + // This is to detect changes in the position or size of the logger + // popup window (if in use). + if ( + popupLoggerBox instanceof Object && + ( + self.screenX !== popupLoggerBox.x || + self.screenY !== popupLoggerBox.y || + self.outerWidth !== popupLoggerBox.w || + self.outerHeight !== popupLoggerBox.h + ) + ) { + popupLoggerBox.x = self.screenX; + popupLoggerBox.y = self.screenY; + popupLoggerBox.w = self.outerWidth; + popupLoggerBox.h = self.outerHeight; + msg.popupLoggerBoxChanged = true; + } + + const response = await vAPI.messaging.send('loggerUI', msg); + + timer = undefined; + onLogBufferRead(response); + readLogBufferLater(); + }; + + const readLogBufferLater = function() { + if ( timer !== undefined ) { return; } + if ( logger.ownerId === undefined ) { return; } + timer = vAPI.setTimeout(readLogBufferNow, 1200); + }; + + readLogBufferNow(); + + return readLogBufferLater; +})(); + +/******************************************************************************/ + +const pageSelectorChanged = function() { + const select = uDom.nodeFromId('pageSelector'); + window.location.replace('#' + select.value); + pageSelectorFromURLHash(); +}; + +const pageSelectorFromURLHash = (( ) => { + let lastHash; + let lastSelectedTabId; + + return function() { + let hash = window.location.hash.slice(1); + let match = /^([^+]+)\+(.+)$/.exec(hash); + if ( match !== null ) { + hash = match[1]; + activeTabId = parseInt(match[2], 10) || 0; + window.location.hash = '#' + hash; + } + + if ( hash !== lastHash ) { + const select = uDom.nodeFromId('pageSelector'); + let option = select.querySelector( + 'option[value="' + hash + '"]' + ); + if ( option === null ) { + hash = '0'; + option = select.options[0]; + } + select.selectedIndex = option.index; + select.value = option.value; + lastHash = hash; + } + + selectedTabId = hash === '_' + ? activeTabId + : parseInt(hash, 10) || 0; + + if ( lastSelectedTabId === selectedTabId ) { return; } + + rowFilterer.filterAll(); + document.dispatchEvent(new Event('tabIdChanged')); + updateCurrentTabTitle(); + uDom('.needdom').toggleClass('disabled', selectedTabId <= 0); + uDom('.needscope').toggleClass('disabled', selectedTabId <= 0); + lastSelectedTabId = selectedTabId; + }; +})(); + +/******************************************************************************/ + +const reloadTab = function(ev) { + const tabId = tabIdFromPageSelector(); + if ( tabId <= 0 ) { return; } + messaging.send('loggerUI', { + what: 'reloadTab', + tabId: tabId, + bypassCache: ev && (ev.ctrlKey || ev.metaKey || ev.shiftKey), + }); +}; + +/******************************************************************************/ +/******************************************************************************/ + +(( ) => { + const reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/; + const reSchemeOnly = /^[\w-]+:$/; + const staticFilterTypes = { + 'beacon': 'ping', + 'doc': 'document', + 'css': 'stylesheet', + 'frame': 'subdocument', + 'object_subrequest': 'object', + 'csp_report': 'other', + }; + const createdStaticFilters = {}; + + let dialog = null; + let targetRow = null; + let targetType; + let targetURLs = []; + let targetFrameHostname; + let targetPageHostname; + let targetTabId; + let targetDomain; + let targetPageDomain; + let targetFrameDomain; + + const uglyTypeFromSelector = function(pane) { + const prettyType = selectValue('select.type.' + pane); + if ( pane === 'static' ) { + return staticFilterTypes[prettyType] || prettyType; + } + return uglyRequestTypes[prettyType] || prettyType; + }; + + const selectNode = function(selector) { + return dialog.querySelector(selector); + }; + + const selectValue = function(selector) { + return selectNode(selector).value || ''; + }; + + const staticFilterNode = function() { + return dialog.querySelector('div.panes > div.static textarea'); + }; + + const onColorsReady = function(response) { + document.body.classList.toggle('dirty', response.dirty); + for ( const url in response.colors ) { + if ( response.colors.hasOwnProperty(url) === false ) { continue; } + const colorEntry = response.colors[url]; + const node = dialog.querySelector('.dynamic .entry .action[data-url="' + url + '"]'); + if ( node === null ) { continue; } + node.classList.toggle('allow', colorEntry.r === 2); + node.classList.toggle('noop', colorEntry.r === 3); + node.classList.toggle('block', colorEntry.r === 1); + node.classList.toggle('own', colorEntry.own); + } + }; + + const colorize = async function() { + const response = await messaging.send('loggerUI', { + what: 'getURLFilteringData', + context: selectValue('select.dynamic.origin'), + urls: targetURLs, + type: uglyTypeFromSelector('dynamic'), + }); + onColorsReady(response); + }; + + const parseStaticInputs = function() { + const options = []; + const block = selectValue('select.static.action') === ''; + let filter = ''; + if ( !block ) { + filter = '@@'; + } + let value = selectValue('select.static.url'); + if ( value !== '' ) { + if ( reSchemeOnly.test(value) ) { + value = `|${value}`; + } else { + if ( value.endsWith('/') ) { + value += '*'; + } else if ( /[/?]/.test(value) === false ) { + value += '^'; + } + value = `||${value}`; + } + } + filter += value; + value = selectValue('select.static.type'); + if ( value !== '' ) { + options.push(uglyTypeFromSelector('static')); + } + value = selectValue('select.static.origin'); + if ( value !== '' ) { + if ( value === targetDomain ) { + options.push('1p'); + } else { + options.push('domain=' + value); + } + } + if ( block && selectValue('select.static.importance') !== '' ) { + options.push('important'); + } + if ( options.length ) { + filter += '$' + options.join(','); + } + staticFilterNode().value = filter; + updateWidgets(); + }; + + const updateWidgets = function() { + const value = staticFilterNode().value; + dialog.querySelector('#createStaticFilter').classList.toggle( + 'disabled', + createdStaticFilters.hasOwnProperty(value) || value === '' + ); + }; + + const onClick = async function(ev) { + const target = ev.target; + const tcl = target.classList; + + // Select a mode + if ( tcl.contains('header') ) { + ev.stopPropagation(); + dialog.setAttribute('data-pane', target.getAttribute('data-pane') ); + return; + } + + // Toggle temporary exception filter + if ( tcl.contains('exceptor') ) { + ev.stopPropagation(); + const status = await messaging.send('loggerUI', { + what: 'toggleTemporaryException', + filter: filterFromTargetRow(), + }); + const row = target.closest('div'); + row.classList.toggle('exceptored', status); + return; + } + + // Create static filter + if ( target.id === 'createStaticFilter' ) { + ev.stopPropagation(); + const value = staticFilterNode().value; + // Avoid duplicates + if ( createdStaticFilters.hasOwnProperty(value) ) { return; } + createdStaticFilters[value] = true; + // https://github.com/uBlockOrigin/uBlock-issues/issues/1281#issuecomment-704217175 + // TODO: + // Figure a way to use the actual document URL. Currently using + // a synthetic URL derived from the document hostname. + if ( value !== '' ) { + messaging.send('loggerUI', { + what: 'createUserFilter', + autoComment: true, + filters: value, + docURL: `https://${targetFrameHostname}/`, + }); + } + updateWidgets(); + return; + } + + // Save url filtering rule(s) + if ( target.id === 'saveRules' ) { + ev.stopPropagation(); + await messaging.send('loggerUI', { + what: 'saveURLFilteringRules', + context: selectValue('select.dynamic.origin'), + urls: targetURLs, + type: uglyTypeFromSelector('dynamic'), + }); + colorize(); + return; + } + + const persist = !!ev.ctrlKey || !!ev.metaKey; + + // Remove url filtering rule + if ( tcl.contains('action') ) { + ev.stopPropagation(); + await messaging.send('loggerUI', { + what: 'setURLFilteringRule', + context: selectValue('select.dynamic.origin'), + url: target.getAttribute('data-url'), + type: uglyTypeFromSelector('dynamic'), + action: 0, + persist: persist, + }); + colorize(); + return; + } + + // add "allow" url filtering rule + if ( tcl.contains('allow') ) { + ev.stopPropagation(); + await messaging.send('loggerUI', { + what: 'setURLFilteringRule', + context: selectValue('select.dynamic.origin'), + url: target.parentNode.getAttribute('data-url'), + type: uglyTypeFromSelector('dynamic'), + action: 2, + persist: persist, + }); + colorize(); + return; + } + + // add "block" url filtering rule + if ( tcl.contains('noop') ) { + ev.stopPropagation(); + await messaging.send('loggerUI', { + what: 'setURLFilteringRule', + context: selectValue('select.dynamic.origin'), + url: target.parentNode.getAttribute('data-url'), + type: uglyTypeFromSelector('dynamic'), + action: 3, + persist: persist, + }); + colorize(); + return; + } + + // add "block" url filtering rule + if ( tcl.contains('block') ) { + ev.stopPropagation(); + await messaging.send('loggerUI', { + what: 'setURLFilteringRule', + context: selectValue('select.dynamic.origin'), + url: target.parentNode.getAttribute('data-url'), + type: uglyTypeFromSelector('dynamic'), + action: 1, + persist: persist, + }); + colorize(); + return; + } + + // Force a reload of the tab + if ( tcl.contains('reload') ) { + ev.stopPropagation(); + messaging.send('loggerUI', { + what: 'reloadTab', + tabId: targetTabId, + }); + return; + } + + // Highlight corresponding element in target web page + if ( tcl.contains('picker') ) { + ev.stopPropagation(); + messaging.send('loggerUI', { + what: 'launchElementPicker', + tabId: targetTabId, + targetURL: 'img\t' + targetURLs[0], + select: true, + }); + return; + } + }; + + const onSelectChange = function(ev) { + const tcl = ev.target.classList; + + if ( tcl.contains('dynamic') ) { + colorize(); + return; + } + + if ( tcl.contains('static') ) { + parseStaticInputs(); + return; + } + }; + + const onInputChange = function() { + updateWidgets(); + }; + + const createPreview = function(type, url) { + const cantPreview = + type !== 'image' || + targetRow.classList.contains('networkRealm') === false || + targetRow.getAttribute('data-status') === '1'; + + // Whether picker can be used + dialog.querySelector('.picker').classList.toggle( + 'hide', + targetTabId < 0 || cantPreview + ); + + // Whether the resource can be previewed + if ( cantPreview ) { return; } + + const container = dialog.querySelector('.preview'); + container.querySelector('span').addEventListener( + 'click', + ( ) => { + const preview = document.createElement('img'); + preview.setAttribute('src', url); + container.replaceChild(preview, container.firstElementChild); + }, + { once: true } + ); + + container.classList.remove('hide'); + }; + + // https://github.com/gorhill/uBlock/issues/1511 + const shortenLongString = function(url, max) { + const urlLen = url.length; + if ( urlLen <= max ) { + return url; + } + const n = urlLen - max - 1; + const i = (urlLen - n) / 2 | 0; + return url.slice(0, i) + '…' + url.slice(i + n); + }; + + // Build list of candidate URLs + const createTargetURLs = function(url) { + const matches = reRFC3986.exec(url); + if ( matches === null ) { return []; } + if ( typeof matches[2] !== 'string' || matches[2].length === 0 ) { + return [ matches[1] ]; + } + // Shortest URL for a valid URL filtering rule + const urls = []; + const rootURL = matches[1] + matches[2]; + urls.unshift(rootURL); + const path = matches[3] || ''; + let pos = path.charAt(0) === '/' ? 1 : 0; + while ( pos < path.length ) { + pos = path.indexOf('/', pos); + if ( pos === -1 ) { + pos = path.length; + } else { + pos += 1; + } + urls.unshift(rootURL + path.slice(0, pos)); + } + const query = matches[4] || ''; + if ( query !== '' ) { + urls.unshift(rootURL + path + query); + } + return urls; + }; + + const filterFromTargetRow = function() { + return targetRow.children[1].textContent; + }; + + const aliasURLFromID = function(id) { + if ( id === '' ) { return ''; } + for ( const entry of loggerEntries ) { + if ( entry.id !== id || entry.aliased ) { continue; } + const fields = entry.textContent.split('\t'); + return fields[6] || ''; + } + return ''; + }; + + const toSummaryPaneFilterNode = async function(receiver, filter) { + receiver.children[1].textContent = filter; + if ( filterAuthorMode !== true ) { return; } + const match = /#@?#/.exec(filter); + if ( match === null ) { return; } + const fragment = document.createDocumentFragment(); + const pos = match.index + match[0].length; + fragment.appendChild(document.createTextNode(filter.slice(0, pos))); + const selector = filter.slice(pos); + const span = document.createElement('span'); + span.className = 'filter'; + span.textContent = selector; + fragment.appendChild(span); + const isTemporaryException = await messaging.send('loggerUI', { + what: 'hasTemporaryException', + filter, + }); + receiver.classList.toggle('exceptored', isTemporaryException); + if ( match[0] === '##' || isTemporaryException ) { + receiver.children[2].style.visibility = ''; + } + receiver.children[1].textContent = ''; + receiver.children[1].appendChild(fragment); + }; + + const fillSummaryPaneFilterList = async function(rows) { + const rawFilter = targetRow.children[1].textContent; + + const nodeFromFilter = function(filter, lists) { + const fragment = document.createDocumentFragment(); + const template = document.querySelector( + '#filterFinderListEntry > span' + ); + for ( const list of lists ) { + const span = template.cloneNode(true); + let a = span.querySelector('a:nth-of-type(1)'); + a.href += encodeURIComponent(list.assetKey); + a.textContent = list.title; + a = span.querySelector('a:nth-of-type(2)'); + if ( list.supportURL ) { + a.setAttribute('href', list.supportURL); + } else { + a.style.display = 'none'; + } + if ( fragment.childElementCount !== 0 ) { + fragment.appendChild(document.createTextNode('\n')); + } + fragment.appendChild(span); + } + return fragment; + }; + + const handleResponse = function(response) { + if ( response instanceof Object === false ) { + response = {}; + } + let bestMatchFilter = ''; + for ( const filter in response ) { + if ( filter.length > bestMatchFilter.length ) { + bestMatchFilter = filter; + } + } + if ( + bestMatchFilter !== '' && + Array.isArray(response[bestMatchFilter]) + ) { + toSummaryPaneFilterNode(rows[0], bestMatchFilter); + rows[1].children[1].appendChild(nodeFromFilter( + bestMatchFilter, + response[bestMatchFilter] + )); + } + // https://github.com/gorhill/uBlock/issues/2179 + if ( rows[1].children[1].childElementCount === 0 ) { + vAPI.i18n.safeTemplateToDOM( + 'loggerStaticFilteringFinderSentence2', + { filter: rawFilter }, + rows[1].children[1] + ); + } + }; + + if ( targetRow.classList.contains('networkRealm') ) { + const response = await messaging.send('loggerUI', { + what: 'listsFromNetFilter', + rawFilter: rawFilter, + }); + handleResponse(response); + } else if ( targetRow.classList.contains('extendedRealm') ) { + const response = await messaging.send('loggerUI', { + what: 'listsFromCosmeticFilter', + url: targetRow.children[6].textContent, + rawFilter: rawFilter, + }); + handleResponse(response); + } + }; + + const fillSummaryPane = function() { + const rows = dialog.querySelectorAll('.pane.details > div'); + const tr = targetRow; + const trcl = tr.classList; + const trch = tr.children; + let text; + // Filter and context + text = filterFromTargetRow(); + if ( + (text !== '') && + (trcl.contains('extendedRealm') || trcl.contains('networkRealm')) + ) { + toSummaryPaneFilterNode(rows[0], text); + } else { + rows[0].style.display = 'none'; + } + // Rule + if ( + (text !== '') && + ( + trcl.contains('dynamicHost') || + trcl.contains('dynamicUrl') || + trcl.contains('switchRealm') + ) + ) { + rows[2].children[1].textContent = text; + } else { + rows[2].style.display = 'none'; + } + // Filter list + if ( trcl.contains('canLookup') ) { + fillSummaryPaneFilterList(rows); + } else { + rows[1].style.display = 'none'; + } + // Root and immediate contexts + const tabhn = tr.getAttribute('data-tabhn') || ''; + const dochn = tr.getAttribute('data-dochn') || ''; + if ( tabhn !== '' && tabhn !== dochn ) { + rows[3].children[1].textContent = tabhn; + } else { + rows[3].style.display = 'none'; + } + if ( dochn !== '' ) { + rows[4].children[1].textContent = dochn; + } else { + rows[4].style.display = 'none'; + } + // Partyness + text = tr.getAttribute('data-parties') || ''; + if ( text !== '' ) { + rows[5].children[1].textContent = `(${trch[4].textContent})\u2002${text}`; + } else { + rows[5].style.display = 'none'; + } + // Type + text = trch[5].textContent; + if ( text !== '' ) { + rows[6].children[1].textContent = text; + } else { + rows[6].style.display = 'none'; + } + // URL + const canonicalURL = trch[6].textContent; + if ( canonicalURL !== '' ) { + const attr = tr.getAttribute('data-status') || ''; + if ( attr !== '' ) { + rows[7].setAttribute('data-status', attr); + if ( tr.hasAttribute('data-modifier') ) { + rows[7].setAttribute('data-modifier', ''); + } + } + rows[7].children[1].appendChild(trch[6].cloneNode(true)); + } else { + rows[7].style.display = 'none'; + } + // Alias URL + text = tr.getAttribute('data-aliasid'); + const aliasURL = text ? aliasURLFromID(text) : ''; + if ( aliasURL !== '' ) { + rows[8].children[1].textContent = + hostnameFromURI(aliasURL) + ' \u21d2\n\u2003' + + hostnameFromURI(canonicalURL); + rows[9].children[1].textContent = aliasURL; + } else { + rows[8].style.display = 'none'; + rows[9].style.display = 'none'; + } + }; + + // Fill dynamic URL filtering pane + const fillDynamicPane = function() { + if ( targetRow.classList.contains('extendedRealm') ) { + return; + } + + // https://github.com/uBlockOrigin/uBlock-issues/issues/662#issuecomment-509220702 + if ( targetType === 'doc' ) { return; } + + // https://github.com/gorhill/uBlock/issues/2469 + if ( targetURLs.length === 0 || reSchemeOnly.test(targetURLs[0]) ) { + return; + } + + // Fill context selector + let select = selectNode('select.dynamic.origin'); + fillOriginSelect(select, targetPageHostname, targetPageDomain); + const option = document.createElement('option'); + option.textContent = '*'; + option.setAttribute('value', '*'); + select.appendChild(option); + + // Fill type selector + select = selectNode('select.dynamic.type'); + select.options[0].textContent = targetType; + select.options[0].setAttribute('value', targetType); + select.selectedIndex = 0; + + // Fill entries + const menuEntryTemplate = dialog.querySelector('.dynamic .toolbar .entry'); + const tbody = dialog.querySelector('.dynamic .entries'); + for ( const targetURL of targetURLs ) { + const menuEntry = menuEntryTemplate.cloneNode(true); + menuEntry.children[0].setAttribute('data-url', targetURL); + menuEntry.children[1].textContent = shortenLongString(targetURL, 128); + tbody.appendChild(menuEntry); + } + + colorize(); + }; + + const fillOriginSelect = function(select, hostname, domain) { + const template = vAPI.i18n('loggerStaticFilteringSentencePartOrigin'); + let value = hostname; + for (;;) { + const option = document.createElement('option'); + option.setAttribute('value', value); + option.textContent = template.replace('{{origin}}', value); + select.appendChild(option); + if ( value === domain ) { break; } + const pos = value.indexOf('.'); + if ( pos === -1 ) { break; } + value = value.slice(pos + 1); + } + }; + + // Fill static filtering pane + const fillStaticPane = function() { + if ( targetRow.classList.contains('extendedRealm') ) { + return; + } + + const template = vAPI.i18n('loggerStaticFilteringSentence'); + const rePlaceholder = /\{\{[^}]+?\}\}/g; + const nodes = []; + let pos = 0; + for (;;) { + const match = rePlaceholder.exec(template); + if ( match === null ) { break; } + if ( pos !== match.index ) { + nodes.push(document.createTextNode(template.slice(pos, match.index))); + } + pos = rePlaceholder.lastIndex; + let select, option; + switch ( match[0] ) { + case '{{br}}': + nodes.push(document.createElement('br')); + break; + + case '{{action}}': + select = document.createElement('select'); + select.className = 'static action'; + option = document.createElement('option'); + option.setAttribute('value', ''); + option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartBlock'); + select.appendChild(option); + option = document.createElement('option'); + option.setAttribute('value', '@@'); + option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartAllow'); + select.appendChild(option); + nodes.push(select); + break; + + case '{{type}}': { + const filterType = staticFilterTypes[targetType] || targetType; + select = document.createElement('select'); + select.className = 'static type'; + option = document.createElement('option'); + option.setAttribute('value', filterType); + option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartType').replace('{{type}}', filterType); + select.appendChild(option); + option = document.createElement('option'); + option.setAttribute('value', ''); + option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartAnyType'); + select.appendChild(option); + nodes.push(select); + break; + } + case '{{url}}': + select = document.createElement('select'); + select.className = 'static url'; + for ( const targetURL of targetURLs ) { + const value = targetURL.replace(/^[a-z-]+:\/\//, ''); + option = document.createElement('option'); + option.setAttribute('value', value); + option.textContent = shortenLongString(value, 128); + select.appendChild(option); + } + nodes.push(select); + break; + + case '{{origin}}': + select = document.createElement('select'); + select.className = 'static origin'; + fillOriginSelect(select, targetFrameHostname, targetFrameDomain); + option = document.createElement('option'); + option.setAttribute('value', ''); + option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartAnyOrigin'); + select.appendChild(option); + nodes.push(select); + break; + + case '{{importance}}': + select = document.createElement('select'); + select.className = 'static importance'; + option = document.createElement('option'); + option.setAttribute('value', ''); + option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartNotImportant'); + select.appendChild(option); + option = document.createElement('option'); + option.setAttribute('value', 'important'); + option.textContent = vAPI.i18n('loggerStaticFilteringSentencePartImportant'); + select.appendChild(option); + nodes.push(select); + break; + + default: + break; + } + } + if ( pos < template.length ) { + nodes.push(document.createTextNode(template.slice(pos))); + } + const parent = dialog.querySelector('div.panes > .static > div:first-of-type'); + for ( let i = 0; i < nodes.length; i++ ) { + parent.appendChild(nodes[i]); + } + parseStaticInputs(); + }; + + const fillDialog = function(domains) { + dialog = modalDialog.create( + '#netFilteringDialog', + ( ) => { + targetURLs = []; + targetRow = null; + dialog = null; + } + ); + dialog.classList.toggle( + 'extendedRealm', + targetRow.classList.contains('extendedRealm') + ); + targetDomain = domains[0]; + targetPageDomain = domains[1]; + targetFrameDomain = domains[2]; + createPreview(targetType, targetURLs[0]); + fillSummaryPane(); + fillDynamicPane(); + fillStaticPane(); + dialog.addEventListener('click', ev => { onClick(ev); }, true); + dialog.addEventListener('change', onSelectChange, true); + dialog.addEventListener('input', onInputChange, true); + modalDialog.show(); + }; + + const toggleOn = async function(ev) { + targetRow = ev.target.closest('.canDetails'); + if ( targetRow === null ) { return; } + ev.stopPropagation(); + targetTabId = tabIdFromAttribute(targetRow); + targetType = targetRow.children[5].textContent.trim() || ''; + targetURLs = createTargetURLs(targetRow.children[6].textContent); + targetPageHostname = targetRow.getAttribute('data-tabhn') || ''; + targetFrameHostname = targetRow.getAttribute('data-dochn') || ''; + + // We need the root domain names for best user experience. + const domains = await messaging.send('loggerUI', { + what: 'getDomainNames', + targets: [ + targetURLs[0], + targetPageHostname, + targetFrameHostname + ], + }); + fillDialog(domains); + }; + + uDom('#netInspector').on( + 'click', + '.canDetails > span:nth-of-type(2),.canDetails > span:nth-of-type(3),.canDetails > span:nth-of-type(5)', + ev => { toggleOn(ev); } + ); +})(); + +/******************************************************************************/ +/******************************************************************************/ + +const rowFilterer = (( ) => { + const userFilters = []; + const builtinFilters = []; + + let masterFilterSwitch = true; + let filters = []; + + const parseInput = function() { + userFilters.length = 0; + + const rawParts = + uDom.nodeFromSelector('#filterInput > input') + .value + .trim() + .split(/\s+/); + const n = rawParts.length; + const reStrs = []; + let not = false; + for ( let i = 0; i < n; i++ ) { + let rawPart = rawParts[i]; + if ( rawPart.charAt(0) === '!' ) { + if ( reStrs.length === 0 ) { + not = true; + } + rawPart = rawPart.slice(1); + } + let reStr = ''; + if ( rawPart.startsWith('/') && rawPart.endsWith('/') ) { + reStr = rawPart.slice(1, -1); + try { + new RegExp(reStr); + } catch(ex) { + reStr = ''; + } + } + if ( reStr === '' ) { + const hardBeg = rawPart.startsWith('|'); + if ( hardBeg ) { + rawPart = rawPart.slice(1); + } + const hardEnd = rawPart.endsWith('|'); + if ( hardEnd ) { + rawPart = rawPart.slice(0, -1); + } + // https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions + reStr = rawPart.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + // https://github.com/orgs/uBlockOrigin/teams/ublock-issues-volunteers/discussions/51 + // Be more flexible when interpreting leading/trailing pipes, + // as leading/trailing pipes are often used in static filters. + if ( hardBeg ) { + reStr = reStr !== '' ? '(?:^|\\s|\\|)' + reStr : '\\|'; + } + if ( hardEnd ) { + reStr += '(?:\\||\\s|$)'; + } + } + if ( reStr === '' ) { continue; } + reStrs.push(reStr); + if ( i < (n - 1) && rawParts[i + 1] === '||' ) { + i += 1; + continue; + } + reStr = reStrs.length === 1 ? reStrs[0] : reStrs.join('|'); + userFilters.push({ + re: new RegExp(reStr, 'i'), + r: !not + }); + reStrs.length = 0; + not = false; + } + filters = builtinFilters.concat(userFilters); + }; + + const filterOne = function(logEntry) { + if ( + logEntry.dead || + selectedTabId !== 0 && + ( + logEntry.tabId === undefined || + logEntry.tabId > 0 && logEntry.tabId !== selectedTabId + ) + ) { + return false; + } + + if ( masterFilterSwitch === false || filters.length === 0 ) { + return true; + } + + // Do not filter out tab load event, they help separate key sections + // of logger. + if ( logEntry.type === 'tabLoad' ) { return true; } + + for ( const f of filters ) { + if ( f.re.test(logEntry.textContent) !== f.r ) { return false; } + } + return true; + }; + + const filterAll = function() { + filteredLoggerEntries = []; + filteredLoggerEntryVoidedCount = 0; + for ( const entry of loggerEntries ) { + if ( filterOne(entry) === false ) { continue; } + filteredLoggerEntries.push(entry); + if ( entry.voided ) { + filteredLoggerEntryVoidedCount += 1; + } + } + viewPort.updateContent(0); + uDom.nodeFromId('filterButton').classList.toggle( + 'active', + filters.length !== 0 + ); + uDom.nodeFromId('clean').classList.toggle( + 'disabled', + filteredLoggerEntryVoidedCount === 0 + ); + uDom.nodeFromId('clear').classList.toggle( + 'disabled', + filteredLoggerEntries.length === 0 + ); + }; + + const onFilterChangedAsync = (( ) => { + let timer; + const commit = ( ) => { + timer = undefined; + parseInput(); + filterAll(); + }; + return ( ) => { + if ( timer !== undefined ) { + clearTimeout(timer); + } + timer = vAPI.setTimeout(commit, 750); + }; + })(); + + const onFilterButton = function() { + masterFilterSwitch = !masterFilterSwitch; + uDom.nodeFromId('netInspector').classList.toggle( + 'f', + masterFilterSwitch + ); + filterAll(); + }; + + const onToggleExtras = function(ev) { + ev.target.classList.toggle('expanded'); + }; + + const onToggleBuiltinExpression = function(ev) { + builtinFilters.length = 0; + + ev.target.classList.toggle('on'); + const filtexElems = ev.currentTarget.querySelectorAll('[data-filtex]'); + const orExprs = []; + let not = false; + for ( const filtexElem of filtexElems ) { + let filtex = filtexElem.getAttribute('data-filtex'); + let active = filtexElem.classList.contains('on'); + if ( filtex === '!' ) { + if ( orExprs.length !== 0 ) { + builtinFilters.push({ + re: new RegExp(orExprs.join('|')), + r: !not + }); + orExprs.length = 0; + } + not = active; + } else if ( active ) { + orExprs.push(filtex); + } + } + if ( orExprs.length !== 0 ) { + builtinFilters.push({ + re: new RegExp(orExprs.join('|')), + r: !not + }); + } + filters = builtinFilters.concat(userFilters); + uDom.nodeFromId('filterExprButton').classList.toggle( + 'active', + builtinFilters.length !== 0 + ); + filterAll(); + }; + + uDom('#filterButton').on('click', onFilterButton); + uDom('#filterInput > input').on('input', onFilterChangedAsync); + uDom('#filterExprButton').on('click', onToggleExtras); + uDom('#filterExprPicker').on('click', '[data-filtex]', onToggleBuiltinExpression); + + // https://github.com/gorhill/uBlock/issues/404 + // Ensure page state is in sync with the state of its various widgets. + parseInput(); + filterAll(); + + return { filterOne, filterAll }; +})(); + +/******************************************************************************/ + +// Discard logger entries to prevent undue memory usage growth. The criteria +// to discard are multiple and user configurable: +// +// - Max number of page load per distinct tab +// - Max number of entry per distinct tab +// - Max entry age + +const rowJanitor = (( ) => { + const tabIdToDiscard = new Set(); + const tabIdToLoadCountMap = new Map(); + const tabIdToEntryCountMap = new Map(); + + let rowIndex = 0; + + const discard = function(timeRemaining) { + const opts = loggerSettings.discard; + const maxLoadCount = typeof opts.maxLoadCount === 'number' + ? opts.maxLoadCount + : 0; + const maxEntryCount = typeof opts.maxEntryCount === 'number' + ? opts.maxEntryCount + : 0; + const obsolete = typeof opts.maxAge === 'number' + ? Date.now() - opts.maxAge * 60000 + : 0; + const deadline = Date.now() + Math.ceil(timeRemaining); + + let i = rowIndex; + // TODO: below should not happen -- remove when confirmed. + if ( i >= loggerEntries.length ) { + i = 0; + } + + if ( i === 0 ) { + tabIdToDiscard.clear(); + tabIdToLoadCountMap.clear(); + tabIdToEntryCountMap.clear(); + } + + let idel = -1; + let bufferedTabId = 0; + let bufferedEntryCount = 0; + let modified = false; + + while ( i < loggerEntries.length ) { + + if ( i % 64 === 0 && Date.now() >= deadline ) { break; } + + const entry = loggerEntries[i]; + const tabId = entry.tabId || 0; + + if ( entry.dead || tabIdToDiscard.has(tabId) ) { + if ( idel === -1 ) { idel = i; } + i += 1; + continue; + } + + if ( maxLoadCount !== 0 && entry.type === 'tabLoad' ) { + let count = (tabIdToLoadCountMap.get(tabId) || 0) + 1; + tabIdToLoadCountMap.set(tabId, count); + if ( count >= maxLoadCount ) { + tabIdToDiscard.add(tabId); + } + } + + if ( maxEntryCount !== 0 ) { + if ( bufferedTabId !== tabId ) { + if ( bufferedEntryCount !== 0 ) { + tabIdToEntryCountMap.set(bufferedTabId, bufferedEntryCount); + } + bufferedTabId = tabId; + bufferedEntryCount = tabIdToEntryCountMap.get(tabId) || 0; + } + bufferedEntryCount += 1; + if ( bufferedEntryCount >= maxEntryCount ) { + tabIdToDiscard.add(bufferedTabId); + } + } + + // Since entries in the logger are chronologically ordered, + // everything below obsolete is to be discarded. + if ( obsolete !== 0 && entry.tstamp <= obsolete ) { + if ( idel === -1 ) { idel = i; } + break; + } + + if ( idel !== -1 ) { + loggerEntries.copyWithin(idel, i); + loggerEntries.length -= i - idel; + idel = -1; + modified = true; + } + + i += 1; + } + + if ( idel !== -1 ) { + loggerEntries.length = idel; + modified = true; + } + + if ( i >= loggerEntries.length ) { i = 0; } + rowIndex = i; + + if ( rowIndex === 0 ) { + tabIdToDiscard.clear(); + tabIdToLoadCountMap.clear(); + tabIdToEntryCountMap.clear(); + } + + if ( modified === false ) { return; } + + rowFilterer.filterAll(); + }; + + const discardAsync = function() { + setTimeout( + ( ) => { + self.requestIdleCallback(deadline => { + discard(deadline.timeRemaining()); + discardAsync(); + }); + }, + 1889 + ); + }; + + // Clear voided entries from the logger's visible content. + // + // Voided entries should be visible only from the "All" option of the + // tab selector. + // + const clean = function() { + if ( filteredLoggerEntries.length === 0 ) { return; } + + let j = 0; + let targetEntry = filteredLoggerEntries[0]; + for ( const entry of loggerEntries ) { + if ( entry !== targetEntry ) { continue; } + if ( entry.voided ) { + entry.dead = true; + } + j += 1; + if ( j === filteredLoggerEntries.length ) { break; } + targetEntry = filteredLoggerEntries[j]; + } + rowFilterer.filterAll(); + }; + + // Clear the logger's visible content. + // + // "Unrelated" entries -- shown for convenience -- will be also cleared + // if and only if the filtered logger content is made entirely of unrelated + // entries. In effect, this means clicking a second time on the eraser will + // cause unrelated entries to also be cleared. + // + const clear = function() { + if ( filteredLoggerEntries.length === 0 ) { return; } + + let clearUnrelated = true; + if ( selectedTabId !== 0 ) { + for ( const entry of filteredLoggerEntries ) { + if ( entry.tabId === selectedTabId ) { + clearUnrelated = false; + break; + } + } + } + + let j = 0; + let targetEntry = filteredLoggerEntries[0]; + for ( const entry of loggerEntries ) { + if ( entry !== targetEntry ) { continue; } + if ( entry.tabId === selectedTabId || clearUnrelated ) { + entry.dead = true; + } + j += 1; + if ( j === filteredLoggerEntries.length ) { break; } + targetEntry = filteredLoggerEntries[j]; + } + rowFilterer.filterAll(); + }; + + discardAsync(); + + uDom.nodeFromId('clean').addEventListener('click', clean); + uDom.nodeFromId('clear').addEventListener('click', clear); + + return { + inserted: function(count) { + if ( rowIndex !== 0 ) { + rowIndex += count; + } + }, + }; +})(); + +/******************************************************************************/ + +const pauseNetInspector = function() { + netInspectorPaused = uDom.nodeFromId('netInspector') + .classList + .toggle('paused'); +}; + +/******************************************************************************/ + +const toggleVCompactView = function() { + uDom.nodeFromSelector('#netInspector .vCompactToggler') + .classList + .toggle('vExpanded'); + viewPort.updateLayout(); +}; + +/******************************************************************************/ + +const popupManager = (( ) => { + let realTabId = 0; + let popup = null; + let popupObserver = null; + + const resizePopup = function() { + if ( popup === null ) { return; } + const popupBody = popup.contentWindow.document.body; + if ( popupBody.clientWidth !== 0 && popup.clientWidth !== popupBody.clientWidth ) { + popup.style.setProperty('width', popupBody.clientWidth + 'px'); + } + if ( popupBody.clientHeight !== 0 && popup.clientHeight !== popupBody.clientHeight ) { + popup.style.setProperty('height', popupBody.clientHeight + 'px'); + } + }; + + const onLoad = function() { + resizePopup(); + popupObserver.observe(popup.contentDocument.body, { + subtree: true, + attributes: true + }); + }; + + const setTabId = function(tabId) { + if ( popup === null ) { return; } + popup.setAttribute('src', 'popup-fenix.html?portrait=1&tabId=' + tabId); + }; + + const onTabIdChanged = function() { + const tabId = tabIdFromPageSelector(); + if ( tabId === 0 ) { return toggleOff(); } + realTabId = tabId; + setTabId(realTabId); + }; + + const toggleOn = function() { + const tabId = tabIdFromPageSelector(); + if ( tabId === 0 ) { return; } + realTabId = tabId; + + popup = uDom.nodeFromId('popupContainer'); + + popup.addEventListener('load', onLoad); + popupObserver = new MutationObserver(resizePopup); + + const parent = uDom.nodeFromId('inspectors'); + const rect = parent.getBoundingClientRect(); + popup.style.setProperty('right', `${rect.right - parent.clientWidth}px`); + parent.classList.add('popupOn'); + + document.addEventListener('tabIdChanged', onTabIdChanged); + + setTabId(realTabId); + uDom.nodeFromId('showpopup').classList.add('active'); + }; + + const toggleOff = function() { + uDom.nodeFromId('showpopup').classList.remove('active'); + document.removeEventListener('tabIdChanged', onTabIdChanged); + uDom.nodeFromId('inspectors').classList.remove('popupOn'); + popup.removeEventListener('load', onLoad); + popupObserver.disconnect(); + popupObserver = null; + popup.setAttribute('src', ''); + + realTabId = 0; + }; + + const api = { + get tabId() { return realTabId || 0; }, + toggleOff: function() { + if ( realTabId !== 0 ) { + toggleOff(); + } + } + }; + + uDom.nodeFromId('showpopup').addEventListener( + 'click', + ( ) => { + void (realTabId === 0 ? toggleOn() : toggleOff()); + } + ); + + return api; +})(); + +/******************************************************************************/ + +// Filter hit stats' MVP ("minimum viable product") +// +const loggerStats = (( ) => { + const enabled = false; + const filterHits = new Map(); + let dialog; + let timer; + const makeRow = function() { + const div = document.createElement('div'); + div.appendChild(document.createElement('span')); + div.appendChild(document.createElement('span')); + return div; + }; + + const fillRow = function(div, entry) { + div.children[0].textContent = entry[1].toLocaleString(); + div.children[1].textContent = entry[0]; + }; + + const updateList = function() { + const sortedHits = Array.from(filterHits).sort((a, b) => { + return b[1] - a[1]; + }); + + const doc = document; + const parent = dialog.querySelector('.sortedEntries'); + let i = 0; + + // Reuse existing rows + for ( let iRow = 0; iRow < parent.childElementCount; iRow++ ) { + if ( i === sortedHits.length ) { break; } + fillRow(parent.children[iRow], sortedHits[i]); + i += 1; + } + + // Append new rows + if ( i < sortedHits.length ) { + const list = doc.createDocumentFragment(); + for ( ; i < sortedHits.length; i++ ) { + const div = makeRow(); + fillRow(div, sortedHits[i]); + list.appendChild(div); + } + parent.appendChild(list); + } + + // Remove extraneous rows + // [Should never happen at this point in this current + // bare-bone implementation] + }; + + const toggleOn = function() { + dialog = modalDialog.create( + '#loggerStatsDialog', + ( ) => { + dialog = undefined; + if ( timer !== undefined ) { + self.cancelIdleCallback(timer); + timer = undefined; + } + } + ); + updateList(); + modalDialog.show(); + }; + + uDom.nodeFromId('loggerStats').addEventListener('click', toggleOn); + + return { + processFilter: function(filter) { + if ( enabled !== true ) { return; } + if ( filter.source !== 'static' && filter.source !== 'cosmetic' ) { + return; + } + filterHits.set(filter.raw, (filterHits.get(filter.raw) || 0) + 1); + if ( dialog === undefined || timer !== undefined ) { return; } + timer = self.requestIdleCallback( + ( ) => { + timer = undefined; + updateList(); + }, + { timeout: 2001 } + ); + } + }; +})(); + +/******************************************************************************/ + +(( ) => { + const lines = []; + const options = { + format: 'list', + encoding: 'markdown', + time: 'anonymous', + }; + let dialog; + + const collectLines = function() { + lines.length = 0; + let t0 = filteredLoggerEntries.length !== 0 + ? filteredLoggerEntries[filteredLoggerEntries.length - 1].tstamp + : 0; + for ( const entry of filteredLoggerEntries ) { + const text = entry.textContent; + const fields = []; + let i = 0; + let beg = text.indexOf('\t'); + if ( beg === 0 ) { continue; } + let timeField = text.slice(0, beg); + if ( options.time === 'anonymous' ) { + timeField = '+' + Math.round((entry.tstamp - t0) / 1000).toString(); + } + fields.push(timeField); + beg += 1; + while ( beg < text.length ) { + let end = text.indexOf('\t', beg); + if ( end === -1 ) { end = text.length; } + fields.push(text.slice(beg, end)); + beg = end + 1; + i += 1; + } + lines.push(fields); + } + }; + + const formatAsPlainTextTable = function() { + const outputAll = []; + for ( const fields of lines ) { + outputAll.push(fields.join('\t')); + } + outputAll.push(''); + return outputAll.join('\n'); + }; + + const formatAsMarkdownTable = function() { + const outputAll = []; + let fieldCount = 0; + for ( const fields of lines ) { + if ( fields.length <= 2 ) { continue; } + if ( fields.length > fieldCount ) { + fieldCount = fields.length; + } + const outputOne = []; + for ( let i = 0; i < fields.length; i++ ) { + const field = fields[i]; + let code = /\b(?:www\.|https?:\/\/)/.test(field) ? '`' : ''; + outputOne.push(` ${code}${field.replace(/\|/g, '\\|')}${code} `); + } + outputAll.push(outputOne.join('|')); + } + if ( fieldCount !== 0 ) { + outputAll.unshift( + `${' |'.repeat(fieldCount-1)} `, + `${':--- |'.repeat(fieldCount-1)}:--- ` + ); + } + return `

Logger output\n\n|${outputAll.join('|\n|')}|\n
\n`; + }; + + const formatAsTable = function() { + if ( options.encoding === 'plain' ) { + return formatAsPlainTextTable(); + } + return formatAsMarkdownTable(); + }; + + const formatAsList = function() { + const outputAll = []; + for ( const fields of lines ) { + const outputOne = []; + for ( let i = 0; i < fields.length; i++ ) { + let str = fields[i]; + if ( str.length === 0 ) { continue; } + outputOne.push(str); + } + outputAll.push(outputOne.join('\n')); + } + let before, between, after; + if ( options.encoding === 'markdown' ) { + const code = '```'; + before = `
Logger output\n\n${code}\n`; + between = `\n${code}\n${code}\n`; + after = `\n${code}\n
\n`; + } else { + before = ''; + between = '\n\n'; + after = '\n'; + } + return `${before}${outputAll.join(between)}${after}`; + }; + + const format = function() { + const output = dialog.querySelector('.output'); + if ( options.format === 'list' ) { + output.textContent = formatAsList(); + } else { + output.textContent = formatAsTable(); + } + }; + + const setRadioButton = function(group, value) { + if ( options.hasOwnProperty(group) === false ) { return; } + const groupEl = dialog.querySelector(`[data-radio="${group}"]`); + const buttonEls = groupEl.querySelectorAll('[data-radio-item]'); + for ( const buttonEl of buttonEls ) { + buttonEl.classList.toggle( + 'on', + buttonEl.getAttribute('data-radio-item') === value + ); + } + options[group] = value; + }; + + const onOption = function(ev) { + const target = ev.target.closest('span[data-i18n]'); + if ( target === null ) { return; } + + // Copy to clipboard + if ( target.matches('.pushbutton') ) { + const textarea = dialog.querySelector('textarea'); + textarea.focus(); + if ( textarea.selectionEnd === textarea.selectionStart ) { + textarea.select(); + } + document.execCommand('copy'); + ev.stopPropagation(); + return; + } + + // Radio buttons + const group = target.closest('[data-radio]'); + if ( group === null ) { return; } + if ( target.matches('span.on') ) { return; } + const item = target.closest('[data-radio-item]'); + if ( item === null ) { return; } + setRadioButton( + group.getAttribute('data-radio'), + item.getAttribute('data-radio-item') + ); + format(); + ev.stopPropagation(); + }; + + const toggleOn = function() { + dialog = modalDialog.create( + '#loggerExportDialog', + ( ) => { + dialog = undefined; + lines.length = 0; + } + ); + + setRadioButton('format', options.format); + setRadioButton('encoding', options.encoding); + + collectLines(); + format(); + + dialog.querySelector('.options').addEventListener( + 'click', + onOption, + { capture: true } + ); + + modalDialog.show(); + }; + + uDom.nodeFromId('loggerExport').addEventListener('click', toggleOn); +})(); + +/******************************************************************************/ + +// TODO: +// - Give some thoughts to: +// - an option to discard immediately filtered out new entries +// - max entry count _per load_ +// +const loggerSettings = (( ) => { + const settings = { + discard: { + maxAge: 240, // global + maxEntryCount: 2000, // per-tab + maxLoadCount: 20, // per-tab + }, + columns: [ true, true, true, true, true, true, true, true ], + linesPerEntry: 4, + }; + + vAPI.localStorage.getItemAsync('loggerSettings').then(value => { + try { + const stored = JSON.parse(value); + if ( typeof stored.discard.maxAge === 'number' ) { + settings.discard.maxAge = stored.discard.maxAge; + } + if ( typeof stored.discard.maxEntryCount === 'number' ) { + settings.discard.maxEntryCount = stored.discard.maxEntryCount; + } + if ( typeof stored.discard.maxLoadCount === 'number' ) { + settings.discard.maxLoadCount = stored.discard.maxLoadCount; + } + if ( typeof stored.linesPerEntry === 'number' ) { + settings.linesPerEntry = stored.linesPerEntry; + } + if ( Array.isArray(stored.columns) ) { + settings.columns = stored.columns; + } + } catch(ex) { + } + }); + + const valueFromInput = function(input, def) { + let value = parseInt(input.value, 10); + if ( isNaN(value) ) { value = def; } + const min = parseInt(input.getAttribute('min'), 10); + if ( isNaN(min) === false ) { + value = Math.max(value, min); + } + const max = parseInt(input.getAttribute('max'), 10); + if ( isNaN(max) === false ) { + value = Math.min(value, max); + } + return value; + }; + + const toggleOn = function() { + const dialog = modalDialog.create( + '#loggerSettingsDialog', + dialog => { + toggleOff(dialog); + } + ); + + // Number inputs + let inputs = dialog.querySelectorAll('input[type="number"]'); + inputs[0].value = settings.discard.maxAge; + inputs[1].value = settings.discard.maxLoadCount; + inputs[2].value = settings.discard.maxEntryCount; + inputs[3].value = settings.linesPerEntry; + inputs[3].addEventListener('input', ev => { + settings.linesPerEntry = valueFromInput(ev.target, 4); + viewPort.updateLayout(); + }); + + // Column checkboxs + const onColumnChanged = ev => { + const input = ev.target; + const i = parseInt(input.getAttribute('data-column'), 10); + settings.columns[i] = input.checked !== true; + viewPort.updateLayout(); + }; + inputs = dialog.querySelectorAll('input[type="checkbox"][data-column]'); + for ( const input of inputs ) { + const i = parseInt(input.getAttribute('data-column'), 10); + input.checked = settings.columns[i] === false; + input.addEventListener('change', onColumnChanged); + } + + modalDialog.show(); + }; + + const toggleOff = function(dialog) { + // Number inputs + let inputs = dialog.querySelectorAll('input[type="number"]'); + settings.discard.maxAge = valueFromInput(inputs[0], 240); + settings.discard.maxLoadCount = valueFromInput(inputs[1], 25); + settings.discard.maxEntryCount = valueFromInput(inputs[2], 2000); + settings.linesPerEntry = valueFromInput(inputs[3], 4); + + // Column checkboxs + inputs = dialog.querySelectorAll('input[type="checkbox"][data-column]'); + for ( const input of inputs ) { + const i = parseInt(input.getAttribute('data-column'), 10); + settings.columns[i] = input.checked !== true; + } + + vAPI.localStorage.setItem( + 'loggerSettings', + JSON.stringify(settings) + ); + + viewPort.updateLayout(); + }; + + uDom.nodeFromId('loggerSettings').addEventListener('click', toggleOn); + + return settings; +})(); + +/******************************************************************************/ + +logger.resize = (function() { + let timer; + + const resize = function() { + const vrect = document.body.getBoundingClientRect(); + const elems = document.querySelectorAll('.vscrollable'); + for ( const elem of elems ) { + const crect = elem.getBoundingClientRect(); + const dh = crect.bottom - vrect.bottom; + if ( dh === 0 ) { continue; } + elem.style.height = Math.ceil(crect.height - dh) + 'px'; + } + }; + + const resizeAsync = function() { + if ( timer !== undefined ) { return; } + timer = self.requestAnimationFrame(( ) => { + timer = undefined; + resize(); + }); + }; + + resizeAsync(); + + window.addEventListener('resize', resizeAsync, { passive: true }); + + return resizeAsync; +})(); + +/******************************************************************************/ + +const grabView = function() { + if ( logger.ownerId === undefined ) { + logger.ownerId = Date.now(); + } + readLogBuffer(); +}; + +const releaseView = function() { + if ( logger.ownerId === undefined ) { return; } + vAPI.messaging.send('loggerUI', { + what: 'releaseView', + ownerId: logger.ownerId, + }); + logger.ownerId = undefined; +}; + +window.addEventListener('pagehide', releaseView); +window.addEventListener('pageshow', grabView); +// https://bugzilla.mozilla.org/show_bug.cgi?id=1398625 +window.addEventListener('beforeunload', releaseView); + +/******************************************************************************/ + +uDom('#pageSelector').on('change', pageSelectorChanged); +uDom('#refresh').on('click', reloadTab); +uDom('#netInspector .vCompactToggler').on('click', toggleVCompactView); +uDom('#pause').on('click', pauseNetInspector); + +// https://github.com/gorhill/uBlock/issues/507 +// Ensure tab selector is in sync with URL hash +pageSelectorFromURLHash(); +window.addEventListener('hashchange', pageSelectorFromURLHash); + +// Start to watch the current window geometry 2 seconds after the document +// is loaded, to be sure no spurious geometry changes will be triggered due +// to the window geometry pontentially not settling fast enough. +if ( self.location.search.includes('popup=1') ) { + window.addEventListener( + 'load', + ( ) => { + setTimeout( + ( ) => { + popupLoggerBox = { + x: self.screenX, + y: self.screenY, + w: self.outerWidth, + h: self.outerHeight, + }; + }, 2000); + }, + { once: true } + ); +} + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/logger.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/logger.js new file mode 100644 index 0000000..9abf6a5 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/logger.js @@ -0,0 +1,88 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-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 +*/ + +'use strict'; + +/******************************************************************************/ + +let buffer = null; +let lastReadTime = 0; +let writePtr = 0; + +// After 30 seconds without being read, a buffer will be considered +// unused, and thus removed from memory. +const logBufferObsoleteAfter = 30 * 1000; + +const janitor = ( ) => { + if ( + buffer !== null && + lastReadTime < (Date.now() - logBufferObsoleteAfter) + ) { + logger.enabled = false; + buffer = null; + writePtr = 0; + logger.ownerId = undefined; + vAPI.messaging.broadcast({ what: 'loggerDisabled' }); + } + if ( buffer !== null ) { + vAPI.setTimeout(janitor, logBufferObsoleteAfter); + } +}; + +const boxEntry = function(details) { + if ( details.tstamp === undefined ) { + details.tstamp = Date.now(); + } + return JSON.stringify(details); +}; + +const logger = { + enabled: false, + ownerId: undefined, + writeOne: function(details) { + if ( buffer === null ) { return; } + const box = boxEntry(details); + if ( writePtr === buffer.length ) { + buffer.push(box); + } else { + buffer[writePtr] = box; + } + writePtr += 1; + }, + readAll: function(ownerId) { + this.ownerId = ownerId; + if ( buffer === null ) { + this.enabled = true; + buffer = []; + vAPI.setTimeout(janitor, logBufferObsoleteAfter); + } + const out = buffer.slice(0, writePtr); + writePtr = 0; + lastReadTime = Date.now(); + return out; + }, +}; + +/******************************************************************************/ + +export default logger; + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/lz4.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/lz4.js new file mode 100644 index 0000000..0c91b2f --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/lz4.js @@ -0,0 +1,205 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2018-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 +*/ + +/* global lz4BlockCodec */ + +'use strict'; + +/******************************************************************************/ + +import µb from './background.js'; + +/******************************************************************************* + + Experimental support for storage compression. + + For background information on the topic, see: + https://github.com/uBlockOrigin/uBlock-issues/issues/141#issuecomment-407737186 + +**/ + +/******************************************************************************/ + +let lz4CodecInstance; +let pendingInitialization; +let textEncoder, textDecoder; +let ttlCount = 0; +let ttlTimer; +let ttlDelay = 60000; + +const init = function() { + ttlDelay = µb.hiddenSettings.autoUpdateAssetFetchPeriod * 1000 + 15000; + if ( lz4CodecInstance === null ) { + return Promise.resolve(null); + } + if ( lz4CodecInstance !== undefined ) { + return Promise.resolve(lz4CodecInstance); + } + if ( pendingInitialization === undefined ) { + let flavor; + if ( µb.hiddenSettings.disableWebAssembly === true ) { + flavor = 'js'; + } + pendingInitialization = lz4BlockCodec.createInstance(flavor) + .then(instance => { + lz4CodecInstance = instance; + pendingInitialization = undefined; + }); + } + return pendingInitialization; +}; + +// We can't shrink memory usage of lz4 codec instances, and in the +// current case memory usage can grow to a significant amount given +// that a single contiguous memory buffer is required to accommodate +// both input and output data. Thus a time-to-live implementation +// which will cause the wasm instance to be forgotten after enough +// time elapse without the instance being used. + +const destroy = function() { + //if ( lz4CodecInstance !== undefined ) { + // console.info( + // 'uBO: freeing lz4-block-codec instance (%s KB)', + // lz4CodecInstance.bytesInUse() >>> 10 + // ); + //} + lz4CodecInstance = undefined; + textEncoder = textDecoder = undefined; + ttlCount = 0; + ttlTimer = undefined; +}; + +const ttlManage = function(count) { + if ( ttlTimer !== undefined ) { + clearTimeout(ttlTimer); + ttlTimer = undefined; + } + ttlCount += count; + if ( ttlCount > 0 ) { return; } + if ( lz4CodecInstance === null ) { return; } + ttlTimer = vAPI.setTimeout(destroy, ttlDelay); +}; + +const encodeValue = function(dataIn) { + if ( !lz4CodecInstance ) { return; } + //let t0 = window.performance.now(); + if ( textEncoder === undefined ) { + textEncoder = new TextEncoder(); + } + const inputArray = textEncoder.encode(dataIn); + const inputSize = inputArray.byteLength; + const outputArray = lz4CodecInstance.encodeBlock(inputArray, 8); + if ( outputArray instanceof Uint8Array === false ) { return; } + outputArray[0] = 0x18; + outputArray[1] = 0x4D; + outputArray[2] = 0x22; + outputArray[3] = 0x04; + outputArray[4] = (inputSize >>> 0) & 0xFF; + outputArray[5] = (inputSize >>> 8) & 0xFF; + outputArray[6] = (inputSize >>> 16) & 0xFF; + outputArray[7] = (inputSize >>> 24) & 0xFF; + //console.info( + // 'uBO: [%s] compressed %d KB => %d KB (%s%%) in %s ms', + // inputArray.byteLength >> 10, + // outputArray.byteLength >> 10, + // (outputArray.byteLength / inputArray.byteLength * 100).toFixed(0), + // (window.performance.now() - t0).toFixed(1) + //); + return outputArray; +}; + +const decodeValue = function(inputArray) { + if ( !lz4CodecInstance ) { return; } + //let t0 = window.performance.now(); + if ( + inputArray[0] !== 0x18 || inputArray[1] !== 0x4D || + inputArray[2] !== 0x22 || inputArray[3] !== 0x04 + ) { + console.error('decodeValue: invalid input array'); + return; + } + const outputSize = + (inputArray[4] << 0) | (inputArray[5] << 8) | + (inputArray[6] << 16) | (inputArray[7] << 24); + const outputArray = lz4CodecInstance.decodeBlock(inputArray, 8, outputSize); + if ( outputArray instanceof Uint8Array === false ) { return; } + if ( textDecoder === undefined ) { + textDecoder = new TextDecoder(); + } + const s = textDecoder.decode(outputArray); + //console.info( + // 'uBO: [%s] decompressed %d KB => %d KB (%s%%) in %s ms', + // inputArray.byteLength >>> 10, + // outputSize >>> 10, + // (inputArray.byteLength / outputSize * 100).toFixed(0), + // (window.performance.now() - t0).toFixed(1) + //); + return s; +}; + +const lz4Codec = { + // Arguments: + // dataIn: must be a string + // Returns: + // A Uint8Array, or the input string as is if compression is not + // possible. + encode: async function(dataIn, serialize = undefined) { + if ( typeof dataIn !== 'string' || dataIn.length < 4096 ) { + return dataIn; + } + ttlManage(1); + await init(); + let dataOut = encodeValue(dataIn); + ttlManage(-1); + if ( serialize instanceof Function ) { + dataOut = await serialize(dataOut); + } + return dataOut || dataIn; + }, + // Arguments: + // dataIn: must be a Uint8Array + // Returns: + // A string, or the input argument as is if decompression is not + // possible. + decode: async function(dataIn, deserialize = undefined) { + if ( deserialize instanceof Function ) { + dataIn = await deserialize(dataIn); + } + if ( dataIn instanceof Uint8Array === false ) { + return dataIn; + } + ttlManage(1); + await init(); + const dataOut = decodeValue(dataIn); + ttlManage(-1); + return dataOut || dataIn; + }, + relinquish: function() { + ttlDelay = 1; + ttlManage(0); + }, +}; + +/******************************************************************************/ + +export default lz4Codec; + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/messaging.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/messaging.js new file mode 100644 index 0000000..ce210c9 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/messaging.js @@ -0,0 +1,1934 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +'use strict'; + +/******************************************************************************/ + +import publicSuffixList from '../lib/publicsuffixlist/publicsuffixlist.js'; +import punycode from '../lib/punycode.js'; + +import cacheStorage from './cachestorage.js'; +import cosmeticFilteringEngine from './cosmetic-filtering.js'; +import htmlFilteringEngine from './html-filtering.js'; +import logger from './logger.js'; +import lz4Codec from './lz4.js'; +import io from './assets.js'; +import scriptletFilteringEngine from './scriptlet-filtering.js'; +import staticExtFilteringEngine from './static-ext-filtering.js'; +import staticFilteringReverseLookup from './reverselookup.js'; +import staticNetFilteringEngine from './static-net-filtering.js'; +import µb from './background.js'; +import webRequest from './traffic.js'; +import { denseBase64 } from './base64-custom.js'; +import { redirectEngine } from './redirect-engine.js'; +import { StaticFilteringParser } from './static-filtering-parser.js'; + +import { + permanentFirewall, + sessionFirewall, + permanentSwitches, + sessionSwitches, + permanentURLFiltering, + sessionURLFiltering, +} from './filtering-engines.js'; + +import { + domainFromHostname, + domainFromURI, + entityFromDomain, + hostnameFromURI, + isNetworkURI, +} from './uri-utils.js'; + +import './benchmarks.js'; + +/******************************************************************************/ + +// https://github.com/uBlockOrigin/uBlock-issues/issues/710 +// Listeners have a name and a "privileged" status. +// The nameless default handler is always deemed "privileged". +// Messages from privileged ports must never relayed to listeners +// which are not privileged. + +/******************************************************************************/ +/******************************************************************************/ + +// Default handler +// privileged + +{ +// >>>>> start of local scope + +const clickToLoad = function(request, sender) { + const { tabId, frameId } = sender; + if ( tabId === undefined || frameId === undefined ) { return false; } + const pageStore = µb.pageStoreFromTabId(tabId); + if ( pageStore === null ) { return false; } + pageStore.clickToLoad(frameId, request.frameURL); + return true; +}; + +const getDomainNames = function(targets) { + return targets.map(target => { + if ( typeof target !== 'string' ) { return ''; } + return target.indexOf('/') !== -1 + ? domainFromURI(target) || '' + : domainFromHostname(target) || target; + }); +}; + +const onMessage = function(request, sender, callback) { + // Async + switch ( request.what ) { + case 'getAssetContent': + // https://github.com/chrisaljoudi/uBlock/issues/417 + io.get(request.url, { + dontCache: true, + needSourceURL: true, + }).then(result => { + callback(result); + }); + return; + + case 'listsFromNetFilter': + staticFilteringReverseLookup.fromNetFilter( + request.rawFilter + ).then(response => { + callback(response); + }); + return; + + case 'listsFromCosmeticFilter': + staticFilteringReverseLookup.fromExtendedFilter( + request + ).then(response => { + callback(response); + }); + return; + + case 'reloadAllFilters': + µb.loadFilterLists().then(( ) => { callback(); }); + return; + + case 'scriptlet': + vAPI.tabs.executeScript(request.tabId, { + file: `/js/scriptlets/${request.scriptlet}.js` + }).then(result => { + callback(result); + }); + return; + + case 'snfeBenchmark': + µb.benchmarkStaticNetFiltering({ redirectEngine }).then(result => { + callback(result); + }); + return; + + default: + break; + } + + // Sync + let response; + + switch ( request.what ) { + case 'applyFilterListSelection': + response = µb.applyFilterListSelection(request); + break; + + case 'clickToLoad': + response = clickToLoad(request, sender); + break; + + case 'createUserFilter': + µb.createUserFilters(request); + break; + + case 'forceUpdateAssets': + µb.scheduleAssetUpdater(0); + io.updateStart({ + delay: µb.hiddenSettings.manualUpdateAssetFetchPeriod + }); + break; + + case 'getAppData': + response = { + name: browser.runtime.getManifest().name, + version: vAPI.app.version, + canBenchmark: µb.hiddenSettings.benchmarkDatasetURL !== 'unset', + }; + break; + + case 'getDomainNames': + response = getDomainNames(request.targets); + break; + + case 'getWhitelist': + response = { + whitelist: µb.arrayFromWhitelist(µb.netWhitelist), + whitelistDefault: µb.netWhitelistDefault, + reBadHostname: µb.reWhitelistBadHostname.source, + reHostnameExtractor: µb.reWhitelistHostnameExtractor.source + }; + break; + + case 'launchElementPicker': + // Launched from some auxiliary pages, clear context menu coords. + µb.epickerArgs.mouse = false; + µb.elementPickerExec(request.tabId, 0, request.targetURL, request.zap); + break; + + case 'gotoURL': + µb.openNewTab(request.details); + break; + + case 'reloadTab': + if ( vAPI.isBehindTheSceneTabId(request.tabId) === false ) { + vAPI.tabs.reload(request.tabId, request.bypassCache === true); + if ( request.select && vAPI.tabs.select ) { + vAPI.tabs.select(request.tabId); + } + } + break; + + case 'setWhitelist': + µb.netWhitelist = µb.whitelistFromString(request.whitelist); + µb.saveWhitelist(); + break; + + case 'toggleHostnameSwitch': + µb.toggleHostnameSwitch(request); + break; + + case 'uiAccentStylesheet': + µb.uiAccentStylesheet = request.stylesheet; + break; + + case 'uiStyles': + response = { + uiAccentCustom: µb.userSettings.uiAccentCustom, + uiAccentCustom0: µb.userSettings.uiAccentCustom0, + uiAccentStylesheet: µb.uiAccentStylesheet, + uiStyles: µb.hiddenSettings.uiStyles, + uiTheme: µb.userSettings.uiTheme, + }; + break; + + case 'userSettings': + response = µb.changeUserSettings(request.name, request.value); + if ( response instanceof Object ) { + if ( vAPI.net.canUncloakCnames !== true ) { + response.cnameUncloakEnabled = undefined; + } + response.canLeakLocalIPAddresses = + vAPI.browserSettings.canLeakLocalIPAddresses === true; + } + break; + + case 'snfeDump': + response = staticNetFilteringEngine.dump(); + break; + + case 'cfeDump': + response = cosmeticFilteringEngine.dump(); + break; + + default: + return vAPI.messaging.UNHANDLED; + } + + callback(response); +}; + +vAPI.messaging.setup(onMessage); + +// <<<<< end of local scope +} + +/******************************************************************************/ +/******************************************************************************/ + +// Channel: +// popupPanel +// privileged + +{ +// >>>>> start of local scope + +const createCounts = ( ) => { + return { + blocked: { any: 0, frame: 0, script: 0 }, + allowed: { any: 0, frame: 0, script: 0 }, + }; +}; + +const getHostnameDict = function(hostnameDetailsMap, out) { + const hnDict = Object.create(null); + const cnMap = []; + + const createDictEntry = (domain, hostname, details) => { + const cname = vAPI.net.canonicalNameFromHostname(hostname); + if ( cname !== undefined ) { + cnMap.push([ cname, hostname ]); + } + hnDict[hostname] = { domain, counts: details.counts }; + }; + + for ( const hnDetails of hostnameDetailsMap.values() ) { + const hostname = hnDetails.hostname; + if ( hnDict[hostname] !== undefined ) { continue; } + const domain = domainFromHostname(hostname) || hostname; + const dnDetails = + hostnameDetailsMap.get(domain) || { counts: createCounts() }; + if ( hnDict[domain] === undefined ) { + createDictEntry(domain, domain, dnDetails); + } + if ( hostname === domain ) { continue; } + createDictEntry(domain, hostname, hnDetails); + } + + out.hostnameDict = hnDict; + out.cnameMap = cnMap; +}; + +const firewallRuleTypes = [ + '*', + 'image', + '3p', + 'inline-script', + '1p-script', + '3p-script', + '3p-frame', +]; + +const getFirewallRules = function(src, out) { + const ruleset = out.firewallRules = {}; + const df = sessionFirewall; + + for ( const type of firewallRuleTypes ) { + const r = df.lookupRuleData('*', '*', type); + if ( r === undefined ) { continue; } + ruleset[`/ * ${type}`] = r; + } + if ( typeof src !== 'string' ) { return; } + + for ( const type of firewallRuleTypes ) { + const r = df.lookupRuleData(src, '*', type); + if ( r === undefined ) { continue; } + ruleset[`. * ${type}`] = r; + } + + const { hostnameDict } = out; + for ( const des in hostnameDict ) { + let r = df.lookupRuleData('*', des, '*'); + if ( r !== undefined ) { ruleset[`/ ${des} *`] = r; } + r = df.lookupRuleData(src, des, '*'); + if ( r !== undefined ) { ruleset[`. ${des} *`] = r; } + } +}; + +const popupDataFromTabId = function(tabId, tabTitle) { + const tabContext = µb.tabContextManager.mustLookup(tabId); + const rootHostname = tabContext.rootHostname; + const µbus = µb.userSettings; + const µbhs = µb.hiddenSettings; + const r = { + advancedUserEnabled: µbus.advancedUserEnabled, + appName: vAPI.app.name, + appVersion: vAPI.app.version, + colorBlindFriendly: µbus.colorBlindFriendly, + cosmeticFilteringSwitch: false, + firewallPaneMinimized: µbus.firewallPaneMinimized, + globalAllowedRequestCount: µb.localSettings.allowedRequestCount, + globalBlockedRequestCount: µb.localSettings.blockedRequestCount, + fontSize: µbhs.popupFontSize, + godMode: µbhs.filterAuthorMode, + netFilteringSwitch: false, + rawURL: tabContext.rawURL, + pageURL: tabContext.normalURL, + pageHostname: rootHostname, + pageDomain: tabContext.rootDomain, + popupBlockedCount: 0, + popupPanelSections: µbus.popupPanelSections, + popupPanelDisabledSections: µbhs.popupPanelDisabledSections, + popupPanelLockedSections: µbhs.popupPanelLockedSections, + popupPanelHeightMode: µbhs.popupPanelHeightMode, + tabId: tabId, + tabTitle: tabTitle, + tooltipsDisabled: µbus.tooltipsDisabled + }; + + if ( µbhs.uiPopupConfig !== 'unset' ) { + r.uiPopupConfig = µbhs.uiPopupConfig; + } + + const pageStore = µb.pageStoreFromTabId(tabId); + if ( pageStore ) { + r.pageCounts = pageStore.counts; + r.netFilteringSwitch = pageStore.getNetFilteringSwitch(); + getHostnameDict(pageStore.getAllHostnameDetails(), r); + r.contentLastModified = pageStore.contentLastModified; + getFirewallRules(rootHostname, r); + r.canElementPicker = isNetworkURI(r.rawURL); + r.noPopups = sessionSwitches.evaluateZ( + 'no-popups', + rootHostname + ); + r.popupBlockedCount = pageStore.popupBlockedCount; + r.noCosmeticFiltering = sessionSwitches.evaluateZ( + 'no-cosmetic-filtering', + rootHostname + ); + r.noLargeMedia = sessionSwitches.evaluateZ( + 'no-large-media', + rootHostname + ); + r.largeMediaCount = pageStore.largeMediaCount; + r.noRemoteFonts = sessionSwitches.evaluateZ( + 'no-remote-fonts', + rootHostname + ); + r.remoteFontCount = pageStore.remoteFontCount; + r.noScripting = sessionSwitches.evaluateZ( + 'no-scripting', + rootHostname + ); + } else { + r.hostnameDict = {}; + getFirewallRules(undefined, r); + } + + r.matrixIsDirty = sessionFirewall.hasSameRules( + permanentFirewall, + rootHostname, + r.hostnameDict + ) === false; + if ( r.matrixIsDirty === false ) { + r.matrixIsDirty = sessionSwitches.hasSameRules( + permanentSwitches, + rootHostname + ) === false; + } + return r; +}; + +const popupDataFromRequest = async function(request) { + if ( request.tabId ) { + return popupDataFromTabId(request.tabId, ''); + } + + // Still no target tab id? Use currently selected tab. + const tab = await vAPI.tabs.getCurrent(); + let tabId = ''; + let tabTitle = ''; + if ( tab instanceof Object ) { + tabId = tab.id; + tabTitle = tab.title || ''; + } + return popupDataFromTabId(tabId, tabTitle); +}; + +const getElementCount = async function(tabId, what) { + const results = await vAPI.tabs.executeScript(tabId, { + allFrames: true, + file: `/js/scriptlets/dom-survey-${what}.js`, + runAt: 'document_end', + }); + + let total = 0; + for ( const count of results ) { + if ( typeof count !== 'number' ) { continue; } + if ( count === -1 ) { return -1; } + total += count; + } + + return total; +}; + +const onMessage = function(request, sender, callback) { + // Async + switch ( request.what ) { + case 'getHiddenElementCount': + getElementCount(request.tabId, 'elements').then(count => { + callback(count); + }); + return; + + case 'getScriptCount': + getElementCount(request.tabId, 'scripts').then(count => { + callback(count); + }); + return; + + case 'getPopupData': + popupDataFromRequest(request).then(popupData => { + callback(popupData); + }); + return; + + default: + break; + } + + // Sync + let response; + + switch ( request.what ) { + case 'hasPopupContentChanged': { + const pageStore = µb.pageStoreFromTabId(request.tabId); + const lastModified = pageStore ? pageStore.contentLastModified : 0; + response = lastModified !== request.contentLastModified; + break; + } + case 'launchReporter': { + const pageStore = µb.pageStoreFromTabId(request.tabId); + if ( pageStore === null ) { break; } + const supportURL = new URL(vAPI.getURL('support.html')); + supportURL.searchParams.set('pageURL', request.pageURL); + supportURL.searchParams.set('popupPanel', request.popupPanel); + µb.openNewTab({ url: supportURL.href, select: true, index: -1 }); + break; + } + case 'revertFirewallRules': + // TODO: use Set() to message around sets of hostnames + sessionFirewall.copyRules( + permanentFirewall, + request.srcHostname, + Object.assign(Object.create(null), request.desHostnames) + ); + sessionSwitches.copyRules( + permanentSwitches, + request.srcHostname + ); + // https://github.com/gorhill/uBlock/issues/188 + cosmeticFilteringEngine.removeFromSelectorCache( + request.srcHostname, + 'net' + ); + µb.updateToolbarIcon(request.tabId, 0b100); + response = popupDataFromTabId(request.tabId); + break; + + case 'saveFirewallRules': + // TODO: use Set() to message around sets of hostnames + if ( + permanentFirewall.copyRules( + sessionFirewall, + request.srcHostname, + Object.assign(Object.create(null), request.desHostnames) + ) + ) { + µb.savePermanentFirewallRules(); + } + if ( + permanentSwitches.copyRules( + sessionSwitches, + request.srcHostname + ) + ) { + µb.saveHostnameSwitches(); + } + break; + + case 'toggleHostnameSwitch': + µb.toggleHostnameSwitch(request); + response = popupDataFromTabId(request.tabId); + break; + + case 'toggleFirewallRule': + µb.toggleFirewallRule(request); + response = popupDataFromTabId(request.tabId); + break; + + case 'toggleNetFiltering': { + const pageStore = µb.pageStoreFromTabId(request.tabId); + if ( pageStore ) { + pageStore.toggleNetFilteringSwitch( + request.url, + request.scope, + request.state + ); + µb.updateToolbarIcon(request.tabId, 0b111); + } + break; + } + default: + return vAPI.messaging.UNHANDLED; + } + + callback(response); +}; + +vAPI.messaging.listen({ + name: 'popupPanel', + listener: onMessage, + privileged: true, +}); + +// <<<<< end of local scope +} + +/******************************************************************************/ +/******************************************************************************/ + +// Channel: +// contentscript +// unprivileged + +{ +// >>>>> start of local scope + +const retrieveContentScriptParameters = async function(sender, request) { + if ( µb.readyToFilter !== true ) { return; } + const { tabId, frameId } = sender; + if ( tabId === undefined || frameId === undefined ) { return; } + + const pageStore = µb.pageStoreFromTabId(tabId); + if ( pageStore === null || pageStore.getNetFilteringSwitch() === false ) { + return; + } + + // A content script may not always be able to successfully look up the + // effective context, hence in such case we try again to look up here + // using cached information about embedded frames. + if ( frameId !== 0 && request.url.startsWith('about:') ) { + request.url = pageStore.getEffectiveFrameURL(sender); + } + + const noSpecificCosmeticFiltering = + pageStore.shouldApplySpecificCosmeticFilters(frameId) === false; + const noGenericCosmeticFiltering = + pageStore.shouldApplyGenericCosmeticFilters(frameId) === false; + + const response = { + collapseBlocked: µb.userSettings.collapseBlocked, + noGenericCosmeticFiltering, + noSpecificCosmeticFiltering, + }; + + request.tabId = tabId; + request.frameId = frameId; + request.hostname = hostnameFromURI(request.url); + request.domain = domainFromHostname(request.hostname); + request.entity = entityFromDomain(request.domain); + + response.specificCosmeticFilters = + cosmeticFilteringEngine.retrieveSpecificSelectors(request, response); + + // The procedural filterer's code is loaded only when needed and must be + // present before returning response to caller. + if ( + Array.isArray(response.specificCosmeticFilters.proceduralFilters) || ( + logger.enabled && + response.specificCosmeticFilters.exceptedFilters.length !== 0 + ) + ) { + await vAPI.tabs.executeScript(tabId, { + allFrames: false, + file: '/js/contentscript-extra.js', + frameId, + matchAboutBlank: true, + runAt: 'document_start', + }); + } + + // https://github.com/uBlockOrigin/uBlock-issues/issues/688#issuecomment-748179731 + // For non-network URIs, scriptlet injection is deferred to here. The + // effective URL is available here in `request.url`. + if ( + µb.canInjectScriptletsNow === false || + isNetworkURI(sender.frameURL) === false + ) { + scriptletFilteringEngine.injectNow(request); + } + + // https://github.com/NanoMeow/QuickReports/issues/6#issuecomment-414516623 + // Inject as early as possible to make the cosmetic logger code less + // sensitive to the removal of DOM nodes which may match injected + // cosmetic filters. + if ( logger.enabled ) { + if ( + noSpecificCosmeticFiltering === false || + noGenericCosmeticFiltering === false + ) { + vAPI.tabs.executeScript(tabId, { + allFrames: false, + file: '/js/scriptlets/cosmetic-logger.js', + frameId, + matchAboutBlank: true, + runAt: 'document_start', + }); + } + } + + return response; +}; + +const onMessage = function(request, sender, callback) { + // Async + switch ( request.what ) { + case 'retrieveContentScriptParameters': + return retrieveContentScriptParameters( + sender, + request + ).then(response => { + callback(response); + }); + default: + break; + } + + const pageStore = µb.pageStoreFromTabId(sender.tabId); + + // Sync + let response; + + switch ( request.what ) { + case 'cosmeticFiltersInjected': + cosmeticFilteringEngine.addToSelectorCache(request); + break; + + case 'getCollapsibleBlockedRequests': + response = { + id: request.id, + hash: request.hash, + netSelectorCacheCountMax: + cosmeticFilteringEngine.netSelectorCacheCountMax, + }; + if ( + µb.userSettings.collapseBlocked && + pageStore && pageStore.getNetFilteringSwitch() + ) { + pageStore.getBlockedResources(request, response); + } + break; + + case 'maybeGoodPopup': + µb.maybeGoodPopup.tabId = sender.tabId; + µb.maybeGoodPopup.url = request.url; + break; + + case 'shouldRenderNoscriptTags': + if ( pageStore === null ) { break; } + const fctxt = µb.filteringContext.fromTabId(sender.tabId); + if ( pageStore.filterScripting(fctxt, undefined) ) { + vAPI.tabs.executeScript(sender.tabId, { + file: '/js/scriptlets/noscript-spoof.js', + frameId: sender.frameId, + runAt: 'document_end', + }); + } + break; + + case 'retrieveGenericCosmeticSelectors': + request.tabId = sender.tabId; + request.frameId = sender.frameId; + response = { + result: cosmeticFilteringEngine.retrieveGenericSelectors(request), + }; + break; + + default: + return vAPI.messaging.UNHANDLED; + } + + callback(response); +}; + +vAPI.messaging.listen({ + name: 'contentscript', + listener: onMessage, +}); + +// <<<<< end of local scope +} + +/******************************************************************************/ +/******************************************************************************/ + +// Channel: +// elementPicker +// unprivileged + +{ +// >>>>> start of local scope + +const onMessage = function(request, sender, callback) { + // Async + switch ( request.what ) { + // The procedural filterer must be present in case the user wants to + // type-in custom filters. + case 'elementPickerArguments': + return vAPI.tabs.executeScript(sender.tabId, { + allFrames: false, + file: '/js/contentscript-extra.js', + frameId: sender.frameId, + matchAboutBlank: true, + runAt: 'document_start', + }).then(( ) => { + callback({ + target: µb.epickerArgs.target, + mouse: µb.epickerArgs.mouse, + zap: µb.epickerArgs.zap, + eprom: µb.epickerArgs.eprom, + pickerURL: vAPI.getURL(`/web_accessible_resources/epicker-ui.html?secret=${vAPI.warSecret()}`), + }); + µb.epickerArgs.target = ''; + }); + default: + break; + } + + // Sync + let response; + + switch ( request.what ) { + case 'elementPickerEprom': + µb.epickerArgs.eprom = request; + break; + + default: + return vAPI.messaging.UNHANDLED; + } + + callback(response); +}; + +vAPI.messaging.listen({ + name: 'elementPicker', + listener: onMessage, +}); + +// <<<<< end of local scope +} + +/******************************************************************************/ +/******************************************************************************/ + +// Channel: +// cloudWidget +// privileged + +{ +// >>>>> start of local scope + +const fromBase64 = function(encoded) { + if ( typeof encoded !== 'string' ) { + return Promise.resolve(encoded); + } + let u8array; + try { + u8array = denseBase64.decode(encoded); + } catch(ex) { + } + return Promise.resolve(u8array !== undefined ? u8array : encoded); +}; + +const toBase64 = function(data) { + const value = data instanceof Uint8Array + ? denseBase64.encode(data) + : data; + return Promise.resolve(value); +}; + +const compress = function(json) { + return lz4Codec.encode(json, toBase64); +}; + +const decompress = function(encoded) { + return lz4Codec.decode(encoded, fromBase64); +}; + +const onMessage = function(request, sender, callback) { + // Cloud storage support is optional. + if ( µb.cloudStorageSupported !== true ) { + callback(); + return; + } + + // Async + switch ( request.what ) { + case 'cloudGetOptions': + vAPI.cloud.getOptions(function(options) { + options.enabled = µb.userSettings.cloudStorageEnabled === true; + callback(options); + }); + return; + + case 'cloudSetOptions': + vAPI.cloud.setOptions(request.options, callback); + return; + + case 'cloudPull': + request.decode = decompress; + return vAPI.cloud.pull(request).then(result => { + callback(result); + }); + + case 'cloudPush': + if ( µb.hiddenSettings.cloudStorageCompression ) { + request.encode = compress; + } + return vAPI.cloud.push(request).then(result => { + callback(result); + }); + + case 'cloudUsed': + return vAPI.cloud.used(request.datakey).then(result => { + callback(result); + }); + + default: + break; + } + + // Sync + let response; + + switch ( request.what ) { + // For when cloud storage is disabled. + case 'cloudPull': + // fallthrough + case 'cloudPush': + break; + + default: + return vAPI.messaging.UNHANDLED; + } + + callback(response); +}; + +vAPI.messaging.listen({ + name: 'cloudWidget', + listener: onMessage, + privileged: true, +}); + +// <<<<< end of local scope +} + +/******************************************************************************/ +/******************************************************************************/ + +// Channel: +// dashboard +// privileged + +{ +// >>>>> start of local scope + +// Settings +const getLocalData = async function() { + const data = Object.assign({}, µb.restoreBackupSettings); + data.storageUsed = await µb.getBytesInUse(); + data.cloudStorageSupported = µb.cloudStorageSupported; + data.privacySettingsSupported = µb.privacySettingsSupported; + return data; +}; + +const backupUserData = async function() { + const userFilters = await µb.loadUserFilters(); + + const userData = { + timeStamp: Date.now(), + version: vAPI.app.version, + userSettings: + µb.getModifiedSettings(µb.userSettings, µb.userSettingsDefault), + selectedFilterLists: µb.selectedFilterLists, + hiddenSettings: + µb.getModifiedSettings(µb.hiddenSettings, µb.hiddenSettingsDefault), + whitelist: µb.arrayFromWhitelist(µb.netWhitelist), + dynamicFilteringString: permanentFirewall.toString(), + urlFilteringString: permanentURLFiltering.toString(), + hostnameSwitchesString: permanentSwitches.toString(), + userFilters: userFilters.content, + }; + + const filename = vAPI.i18n('aboutBackupFilename') + .replace('{{datetime}}', µb.dateNowToSensibleString()) + .replace(/ +/g, '_'); + µb.restoreBackupSettings.lastBackupFile = filename; + µb.restoreBackupSettings.lastBackupTime = Date.now(); + vAPI.storage.set(µb.restoreBackupSettings); + + const localData = await getLocalData(); + + return { localData, userData }; +}; + +const restoreUserData = async function(request) { + const userData = request.userData; + + // https://github.com/LiCybora/NanoDefenderFirefox/issues/196 + // Backup data could be from Chromium platform or from an older + // Firefox version. + if ( + vAPI.webextFlavor.soup.has('firefox') && + vAPI.app.intFromVersion(userData.version) <= 1031003011 + ) { + userData.hostnameSwitchesString += '\nno-csp-reports: * true'; + } + + // List of external lists is meant to be a string. + if ( Array.isArray(userData.externalLists) ) { + userData.externalLists = userData.externalLists.join('\n'); + } + + // https://github.com/chrisaljoudi/uBlock/issues/1102 + // Ensure all currently cached assets are flushed from storage AND memory. + io.rmrf(); + + // If we are going to restore all, might as well wipe out clean local + // storages + await Promise.all([ + cacheStorage.clear(), + vAPI.storage.clear(), + ]); + + // Restore block stats + µb.saveLocalSettings(); + + // Restore user data + vAPI.storage.set(userData.userSettings); + + // Restore advanced settings. + let hiddenSettings = userData.hiddenSettings; + if ( hiddenSettings instanceof Object === false ) { + hiddenSettings = µb.hiddenSettingsFromString( + userData.hiddenSettingsString || '' + ); + } + // Discard unknown setting or setting with default value. + for ( const key in hiddenSettings ) { + if ( + µb.hiddenSettingsDefault.hasOwnProperty(key) === false || + hiddenSettings[key] === µb.hiddenSettingsDefault[key] + ) { + delete hiddenSettings[key]; + } + } + + // Whitelist directives can be represented as an array or as a + // (eventually to be deprecated) string. + let whitelist = userData.whitelist; + if ( + Array.isArray(whitelist) === false && + typeof userData.netWhitelist === 'string' && + userData.netWhitelist !== '' + ) { + whitelist = userData.netWhitelist.split('\n'); + } + vAPI.storage.set({ + hiddenSettings, + netWhitelist: whitelist || [], + dynamicFilteringString: userData.dynamicFilteringString || '', + urlFilteringString: userData.urlFilteringString || '', + hostnameSwitchesString: userData.hostnameSwitchesString || '', + lastRestoreFile: request.file || '', + lastRestoreTime: Date.now(), + lastBackupFile: '', + lastBackupTime: 0 + }); + µb.saveUserFilters(userData.userFilters); + if ( Array.isArray(userData.selectedFilterLists) ) { + await µb.saveSelectedFilterLists(userData.selectedFilterLists); + } + + vAPI.app.restart(); +}; + +// Remove all stored data but keep global counts, people can become +// quite attached to numbers +const resetUserData = async function() { + await Promise.all([ + cacheStorage.clear(), + vAPI.storage.clear(), + ]); + + await µb.saveLocalSettings(); + + vAPI.app.restart(); +}; + +// Filter lists +const prepListEntries = function(entries) { + for ( const k in entries ) { + if ( entries.hasOwnProperty(k) === false ) { continue; } + const entry = entries[k]; + if ( typeof entry.supportURL === 'string' && entry.supportURL !== '' ) { + entry.supportName = hostnameFromURI(entry.supportURL); + } else if ( typeof entry.homeURL === 'string' && entry.homeURL !== '' ) { + const hn = hostnameFromURI(entry.homeURL); + entry.supportURL = `http://${hn}/`; + entry.supportName = domainFromHostname(hn); + } + } +}; + +const getLists = async function(callback) { + const r = { + autoUpdate: µb.userSettings.autoUpdate, + available: null, + cache: null, + cosmeticFilterCount: cosmeticFilteringEngine.getFilterCount(), + current: µb.availableFilterLists, + ignoreGenericCosmeticFilters: µb.userSettings.ignoreGenericCosmeticFilters, + isUpdating: io.isUpdating(), + netFilterCount: staticNetFilteringEngine.getFilterCount(), + parseCosmeticFilters: µb.userSettings.parseAllABPHideFilters, + suspendUntilListsAreLoaded: µb.userSettings.suspendUntilListsAreLoaded, + userFiltersPath: µb.userFiltersPath + }; + const [ lists, metadata ] = await Promise.all([ + µb.getAvailableLists(), + io.metadata(), + ]); + r.available = lists; + prepListEntries(r.available); + r.cache = metadata; + prepListEntries(r.cache); + callback(r); +}; + +// My filters + +// TODO: also return origin of embedded frames? +const getOriginHints = function() { + const out = new Set(); + for ( const tabId of µb.pageStores.keys() ) { + if ( tabId === -1 ) { continue; } + const tabContext = µb.tabContextManager.lookup(tabId); + if ( tabContext === null ) { continue; } + let { rootDomain, rootHostname } = tabContext; + if ( rootDomain.endsWith('-scheme') ) { continue; } + const isPunycode = rootHostname.includes('xn--'); + out.add(isPunycode ? punycode.toUnicode(rootDomain) : rootDomain); + if ( rootHostname === rootDomain ) { continue; } + out.add(isPunycode ? punycode.toUnicode(rootHostname) : rootHostname); + } + return Array.from(out); +}; + +// My rules +const getRules = function() { + return { + permanentRules: + permanentFirewall.toArray().concat( + permanentSwitches.toArray(), + permanentURLFiltering.toArray() + ), + sessionRules: + sessionFirewall.toArray().concat( + sessionSwitches.toArray(), + sessionURLFiltering.toArray() + ), + pslSelfie: publicSuffixList.toSelfie(), + }; +}; + +const modifyRuleset = function(details) { + let swRuleset, hnRuleset, urlRuleset; + if ( details.permanent ) { + swRuleset = permanentSwitches; + hnRuleset = permanentFirewall; + urlRuleset = permanentURLFiltering; + } else { + swRuleset = sessionSwitches; + hnRuleset = sessionFirewall; + urlRuleset = sessionURLFiltering; + } + let toRemove = new Set(details.toRemove.trim().split(/\s*[\n\r]+\s*/)); + for ( let rule of toRemove ) { + if ( rule === '' ) { continue; } + let parts = rule.split(/\s+/); + if ( hnRuleset.removeFromRuleParts(parts) === false ) { + if ( swRuleset.removeFromRuleParts(parts) === false ) { + urlRuleset.removeFromRuleParts(parts); + } + } + } + let toAdd = new Set(details.toAdd.trim().split(/\s*[\n\r]+\s*/)); + for ( let rule of toAdd ) { + if ( rule === '' ) { continue; } + let parts = rule.split(/\s+/); + if ( hnRuleset.addFromRuleParts(parts) === false ) { + if ( swRuleset.addFromRuleParts(parts) === false ) { + urlRuleset.addFromRuleParts(parts); + } + } + } + if ( details.permanent ) { + if ( swRuleset.changed ) { + µb.saveHostnameSwitches(); + swRuleset.changed = false; + } + if ( hnRuleset.changed ) { + µb.savePermanentFirewallRules(); + hnRuleset.changed = false; + } + if ( urlRuleset.changed ) { + µb.savePermanentURLFilteringRules(); + urlRuleset.changed = false; + } + } +}; + +// Shortcuts +const getShortcuts = function(callback) { + if ( µb.canUseShortcuts === false ) { + return callback([]); + } + + vAPI.commands.getAll(commands => { + let response = []; + for ( let command of commands ) { + let desc = command.description; + let match = /^__MSG_(.+?)__$/.exec(desc); + if ( match !== null ) { + desc = vAPI.i18n(match[1]); + } + if ( desc === '' ) { continue; } + command.description = desc; + response.push(command); + } + callback(response); + }); +}; + +const setShortcut = function(details) { + if ( µb.canUpdateShortcuts === false ) { return; } + if ( details.shortcut === undefined ) { + vAPI.commands.reset(details.name); + µb.commandShortcuts.delete(details.name); + } else { + vAPI.commands.update({ name: details.name, shortcut: details.shortcut }); + µb.commandShortcuts.set(details.name, details.shortcut); + } + vAPI.storage.set({ commandShortcuts: Array.from(µb.commandShortcuts) }); +}; + +// Support +const getSupportData = async function() { + const diffArrays = function(modified, original) { + const modifiedSet = new Set(modified); + const originalSet = new Set(original); + let added = []; + let removed = []; + for ( const item of modifiedSet ) { + if ( originalSet.has(item) ) { continue; } + added.push(item); + } + for ( const item of originalSet ) { + if ( modifiedSet.has(item) ) { continue; } + removed.push(item); + } + if ( added.length === 0 ) { + added = undefined; + } + if ( removed.length === 0 ) { + removed = undefined; + } + if ( added !== undefined || removed !== undefined ) { + return { added, removed }; + } + }; + + const modifiedUserSettings = µb.getModifiedSettings( + µb.userSettings, + µb.userSettingsDefault + ); + + const modifiedHiddenSettings = µb.getModifiedSettings( + µb.hiddenSettings, + µb.hiddenSettingsDefault + ); + + let filterset = []; + const userFilters = await µb.loadUserFilters(); + for ( const line of userFilters.content.split(/\s*\n+\s*/) ) { + if ( /^($|![^#])/.test(line) ) { continue; } + filterset.push(line); + } + + const lists = µb.availableFilterLists; + let defaultListset = {}; + let addedListset = {}; + let removedListset = {}; + for ( const listKey in lists ) { + if ( lists.hasOwnProperty(listKey) === false ) { continue; } + const list = lists[listKey]; + if ( list.content !== 'filters' ) { continue; } + const used = µb.selectedFilterLists.includes(listKey); + const listDetails = []; + if ( used ) { + if ( typeof list.entryCount === 'number' ) { + listDetails.push(`${list.entryCount}-${list.entryCount-list.entryUsedCount}`); + } + if ( typeof list.writeTime !== 'number' || list.writeTime === 0 ) { + listDetails.push('never'); + } else { + const delta = (Date.now() - list.writeTime) / 1000 | 0; + const days = (delta / 86400) | 0; + const hours = (delta % 86400) / 3600 | 0; + const minutes = (delta % 3600) / 60 | 0; + const parts = []; + if ( days > 0 ) { parts.push(`${days}d`); } + if ( hours > 0 ) { parts.push(`${hours}h`); } + if ( minutes > 0 ) { parts.push(`${minutes}m`); } + if ( parts.length === 0 ) { parts.push('now'); } + listDetails.push(parts.join('.')); + } + } + if ( list.isDefault || listKey === µb.userFiltersPath ) { + if ( used ) { + defaultListset[listKey] = listDetails.join(', '); + } else { + removedListset[listKey] = null; + } + } else if ( used ) { + addedListset[listKey] = listDetails.join(', '); + } + } + if ( Object.keys(defaultListset).length === 0 ) { + defaultListset = undefined; + } + if ( Object.keys(addedListset).length === 0 ) { + addedListset = undefined; + } else if ( Object.keys(addedListset).length > 20 ) { + const added = Object.keys(addedListset); + const truncated = added.slice(20); + for ( const key of truncated ) { + delete addedListset[key]; + } + addedListset[`[${truncated.length} lists not shown]`] = '[too many]'; + } + if ( Object.keys(removedListset).length === 0 ) { + removedListset = undefined; + } + + let browserFamily = (( ) => { + if ( vAPI.webextFlavor.soup.has('firefox') ) { return 'Firefox'; } + if ( vAPI.webextFlavor.soup.has('chromium') ) { return 'Chromium'; } + return 'Unknown'; + })(); + if ( vAPI.webextFlavor.soup.has('mobile') ) { + browserFamily += ' Mobile'; + } + + return { + [`${vAPI.app.name}`]: `${vAPI.app.version}`, + [`${browserFamily}`]: `${vAPI.webextFlavor.major}`, + 'filterset (summary)': { + network: staticNetFilteringEngine.getFilterCount(), + cosmetic: cosmeticFilteringEngine.getFilterCount(), + scriptlet: scriptletFilteringEngine.getFilterCount(), + html: htmlFilteringEngine.getFilterCount(), + }, + 'listset (total-discarded, last updated)': { + removed: removedListset, + added: addedListset, + default: defaultListset, + }, + 'filterset (user)': filterset, + trustedset: diffArrays( + µb.arrayFromWhitelist(µb.netWhitelist), + µb.netWhitelistDefault + ), + switchRuleset: diffArrays( + sessionSwitches.toArray(), + µb.hostnameSwitchesDefault + ), + hostRuleset: diffArrays( + sessionFirewall.toArray(), + µb.dynamicFilteringDefault + ), + urlRuleset: diffArrays( + sessionURLFiltering.toArray(), + [] + ), + modifiedUserSettings, + modifiedHiddenSettings, + supportStats: µb.supportStats, + }; +}; + +const onMessage = function(request, sender, callback) { + // Async + switch ( request.what ) { + case 'backupUserData': + return backupUserData().then(data => { + callback(data); + }); + + case 'getLists': + return getLists(callback); + + case 'getLocalData': + return getLocalData().then(localData => { + callback(localData); + }); + + case 'getShortcuts': + return getShortcuts(callback); + + case 'getSupportData': { + getSupportData().then(response => { + callback(response); + }); + return; + } + + case 'readUserFilters': + return µb.loadUserFilters().then(result => { + callback(result); + }); + + case 'writeUserFilters': + return µb.saveUserFilters(request.content).then(result => { + callback(result); + }); + + default: + break; + } + + // Sync + let response; + + switch ( request.what ) { + case 'dashboardConfig': + response = { + canUpdateShortcuts: µb.canUpdateShortcuts, + noDashboard: µb.noDashboard, + }; + break; + + case 'getAutoCompleteDetails': + response = {}; + if ( (request.hintUpdateToken || 0) === 0 ) { + response.redirectResources = redirectEngine.getResourceDetails(); + response.preparseDirectiveTokens = µb.preparseDirectives.getTokens(); + response.preparseDirectiveHints = µb.preparseDirectives.getHints(); + response.expertMode = µb.hiddenSettings.filterAuthorMode; + } + if ( request.hintUpdateToken !== µb.pageStoresToken ) { + response.originHints = getOriginHints(); + response.hintUpdateToken = µb.pageStoresToken; + } + break; + + case 'getRules': + response = getRules(); + break; + + case 'modifyRuleset': + // https://github.com/chrisaljoudi/uBlock/issues/772 + cosmeticFilteringEngine.removeFromSelectorCache('*'); + modifyRuleset(request); + response = getRules(); + break; + + case 'purgeAllCaches': + if ( request.hard ) { + io.remove(/./); + } else { + io.purge(/./, 'public_suffix_list.dat'); + } + break; + + case 'purgeCache': + io.purge(request.assetKey); + io.remove('compiled/' + request.assetKey); + break; + + case 'readHiddenSettings': + response = { + 'default': µb.hiddenSettingsDefault, + 'admin': µb.hiddenSettingsAdmin, + 'current': µb.hiddenSettings, + }; + break; + + case 'restoreUserData': + restoreUserData(request); + break; + + case 'resetUserData': + resetUserData(); + break; + + case 'setShortcut': + setShortcut(request); + break; + + case 'writeHiddenSettings': + µb.changeHiddenSettings(µb.hiddenSettingsFromString(request.content)); + break; + + default: + return vAPI.messaging.UNHANDLED; + } + + callback(response); +}; + +vAPI.messaging.listen({ + name: 'dashboard', + listener: onMessage, + privileged: true, +}); + +// <<<<< end of local scope +} + +/******************************************************************************/ +/******************************************************************************/ + +// Channel: +// loggerUI +// privileged + +{ +// >>>>> start of local scope + +const extensionOriginURL = vAPI.getURL(''); +const documentBlockedURL = vAPI.getURL('document-blocked.html'); + +const getLoggerData = async function(details, activeTabId, callback) { + const response = { + activeTabId, + colorBlind: µb.userSettings.colorBlindFriendly, + entries: logger.readAll(details.ownerId), + filterAuthorMode: µb.hiddenSettings.filterAuthorMode, + tabIdsToken: µb.pageStoresToken, + tooltips: µb.userSettings.tooltipsDisabled === false + }; + if ( µb.pageStoresToken !== details.tabIdsToken ) { + const tabIds = new Map(); + for ( const [ tabId, pageStore ] of µb.pageStores ) { + const { rawURL } = pageStore; + if ( + rawURL.startsWith(extensionOriginURL) === false || + rawURL.startsWith(documentBlockedURL) + ) { + tabIds.set(tabId, pageStore.title); + } + } + response.tabIds = Array.from(tabIds); + } + if ( activeTabId ) { + const pageStore = µb.pageStoreFromTabId(activeTabId); + const rawURL = pageStore && pageStore.rawURL; + if ( + rawURL === null || + rawURL.startsWith(extensionOriginURL) && + rawURL.startsWith(documentBlockedURL) === false + ) { + response.activeTabId = undefined; + } + } + if ( details.popupLoggerBoxChanged && vAPI.windows instanceof Object ) { + const tabs = await vAPI.tabs.query({ + url: vAPI.getURL('/logger-ui.html?popup=1') + }); + if ( tabs.length !== 0 ) { + const win = await vAPI.windows.get(tabs[0].windowId); + if ( win === null ) { return; } + vAPI.localStorage.setItem('popupLoggerBox', JSON.stringify({ + left: win.left, + top: win.top, + width: win.width, + height: win.height, + })); + } + } + callback(response); +}; + +const getURLFilteringData = function(details) { + const colors = {}; + const response = { + dirty: false, + colors: colors + }; + const suf = sessionURLFiltering; + const puf = permanentURLFiltering; + const urls = details.urls; + const context = details.context; + const type = details.type; + for ( const url of urls ) { + const colorEntry = colors[url] = { r: 0, own: false }; + if ( suf.evaluateZ(context, url, type).r !== 0 ) { + colorEntry.r = suf.r; + colorEntry.own = suf.r !== 0 && + suf.context === context && + suf.url === url && + suf.type === type; + } + if ( response.dirty ) { continue; } + puf.evaluateZ(context, url, type); + response.dirty = colorEntry.own !== ( + puf.r !== 0 && + puf.context === context && + puf.url === url && + puf.type === type + ); + } + return response; +}; + +const compileTemporaryException = function(filter) { + const parser = new StaticFilteringParser(); + parser.analyze(filter); + if ( parser.shouldDiscard() ) { return; } + return staticExtFilteringEngine.compileTemporary(parser); +}; + +const toggleTemporaryException = function(details) { + const result = compileTemporaryException(details.filter); + if ( result === undefined ) { return false; } + const { session, selector } = result; + if ( session.has(1, selector) ) { + session.remove(1, selector); + return false; + } + session.add(1, selector); + return true; +}; + +const hasTemporaryException = function(details) { + const result = compileTemporaryException(details.filter); + if ( result === undefined ) { return false; } + const { session, selector } = result; + return session && session.has(1, selector); +}; + +const onMessage = function(request, sender, callback) { + // Async + switch ( request.what ) { + case 'readAll': + if ( + logger.ownerId !== undefined && + logger.ownerId !== request.ownerId + ) { + return callback({ unavailable: true }); + } + vAPI.tabs.getCurrent().then(tab => { + getLoggerData(request, tab && tab.id, callback); + }); + return; + + default: + break; + } + + // Sync + let response; + + switch ( request.what ) { + case 'hasTemporaryException': + response = hasTemporaryException(request); + break; + + case 'releaseView': + if ( request.ownerId === logger.ownerId ) { + logger.ownerId = undefined; + } + break; + + case 'saveURLFilteringRules': + response = permanentURLFiltering.copyRules( + sessionURLFiltering, + request.context, + request.urls, + request.type + ); + if ( response ) { + µb.savePermanentURLFilteringRules(); + } + break; + + case 'setURLFilteringRule': + µb.toggleURLFilteringRule(request); + break; + + case 'getURLFilteringData': + response = getURLFilteringData(request); + break; + + case 'toggleTemporaryException': + response = toggleTemporaryException(request); + break; + + default: + return vAPI.messaging.UNHANDLED; + } + + callback(response); +}; + +vAPI.messaging.listen({ + name: 'loggerUI', + listener: onMessage, + privileged: true, +}); + +// <<<<< end of local scope +} + +/******************************************************************************/ +/******************************************************************************/ + +// Channel: +// documentBlocked +// privileged + +{ +// >>>>> start of local scope + +const onMessage = function(request, sender, callback) { + const tabId = sender.tabId || 0; + + // Async + switch ( request.what ) { + default: + break; + } + + // Sync + let response; + + switch ( request.what ) { + case 'closeThisTab': + vAPI.tabs.remove(tabId); + break; + + case 'temporarilyWhitelistDocument': + webRequest.strictBlockBypass(request.hostname); + break; + + default: + return vAPI.messaging.UNHANDLED; + } + + callback(response); +}; + +vAPI.messaging.listen({ + name: 'documentBlocked', + listener: onMessage, + privileged: true, +}); + +// <<<<< end of local scope +} + +/******************************************************************************/ +/******************************************************************************/ + +// Channel: +// scriptlets +// unprivileged + +{ +// >>>>> start of local scope + +const logCosmeticFilters = function(tabId, details) { + if ( logger.enabled === false ) { return; } + + const filter = { source: 'cosmetic', raw: '' }; + const fctxt = µb.filteringContext.duplicate(); + fctxt.fromTabId(tabId) + .setRealm('cosmetic') + .setType('dom') + .setURL(details.frameURL) + .setDocOriginFromURL(details.frameURL) + .setFilter(filter); + for ( const selector of details.matchedSelectors.sort() ) { + filter.raw = selector; + fctxt.toLogger(); + } +}; + +const logCSPViolations = function(pageStore, request) { + if ( logger.enabled === false || pageStore === null ) { + return false; + } + if ( request.violations.length === 0 ) { + return true; + } + + const fctxt = µb.filteringContext.duplicate(); + fctxt.fromTabId(pageStore.tabId) + .setRealm('network') + .setDocOriginFromURL(request.docURL) + .setURL(request.docURL); + + let cspData = pageStore.extraData.get('cspData'); + if ( cspData === undefined ) { + cspData = new Map(); + + const staticDirectives = + staticNetFilteringEngine.matchAndFetchModifiers(fctxt, 'csp'); + if ( staticDirectives !== undefined ) { + for ( const directive of staticDirectives ) { + if ( directive.result !== 1 ) { continue; } + cspData.set(directive.value, directive.logData()); + } + } + + fctxt.type = 'inline-script'; + fctxt.filter = undefined; + if ( pageStore.filterRequest(fctxt) === 1 ) { + cspData.set(µb.cspNoInlineScript, fctxt.filter); + } + + fctxt.type = 'script'; + fctxt.filter = undefined; + if ( pageStore.filterScripting(fctxt, true) === 1 ) { + cspData.set(µb.cspNoScripting, fctxt.filter); + } + + fctxt.type = 'inline-font'; + fctxt.filter = undefined; + if ( pageStore.filterRequest(fctxt) === 1 ) { + cspData.set(µb.cspNoInlineFont, fctxt.filter); + } + + if ( cspData.size === 0 ) { return false; } + + pageStore.extraData.set('cspData', cspData); + } + + const typeMap = logCSPViolations.policyDirectiveToTypeMap; + for ( const json of request.violations ) { + const violation = JSON.parse(json); + let type = typeMap.get(violation.directive); + if ( type === undefined ) { continue; } + const logData = cspData.get(violation.policy); + if ( logData === undefined ) { continue; } + if ( /^[\w.+-]+:\/\//.test(violation.url) === false ) { + violation.url = request.docURL; + if ( type === 'script' ) { type = 'inline-script'; } + else if ( type === 'font' ) { type = 'inline-font'; } + } + // The resource was blocked as a result of applying a CSP directive + // elsewhere rather than to the resource itself. + logData.modifier = undefined; + fctxt.setURL(violation.url) + .setType(type) + .setFilter(logData) + .toLogger(); + } + + return true; +}; + +logCSPViolations.policyDirectiveToTypeMap = new Map([ + [ 'img-src', 'image' ], + [ 'connect-src', 'xmlhttprequest' ], + [ 'font-src', 'font' ], + [ 'frame-src', 'sub_frame' ], + [ 'media-src', 'media' ], + [ 'object-src', 'object' ], + [ 'script-src', 'script' ], + [ 'script-src-attr', 'script' ], + [ 'script-src-elem', 'script' ], + [ 'style-src', 'stylesheet' ], + [ 'style-src-attr', 'stylesheet' ], + [ 'style-src-elem', 'stylesheet' ], +]); + +const onMessage = function(request, sender, callback) { + const tabId = sender.tabId || 0; + const pageStore = µb.pageStoreFromTabId(tabId); + + // Async + switch ( request.what ) { + default: + break; + } + + // Sync + let response; + + switch ( request.what ) { + case 'inlinescriptFound': + if ( logger.enabled && pageStore !== null ) { + const fctxt = µb.filteringContext.duplicate(); + fctxt.fromTabId(tabId) + .setType('inline-script') + .setURL(request.docURL) + .setDocOriginFromURL(request.docURL); + if ( pageStore.filterRequest(fctxt) === 0 ) { + fctxt.setRealm('network').toLogger(); + } + } + break; + + case 'logCosmeticFilteringData': + logCosmeticFilters(tabId, request); + break; + + case 'securityPolicyViolation': + response = logCSPViolations(pageStore, request); + break; + + case 'temporarilyAllowLargeMediaElement': + if ( pageStore !== null ) { + pageStore.allowLargeMediaElementsUntil = Date.now() + 5000; + } + break; + + case 'subscribeTo': + // https://github.com/uBlockOrigin/uBlock-issues/issues/1797 + if ( /^(file|https?):\/\//.test(request.location) === false ) { break; } + const url = encodeURIComponent(request.location); + const title = encodeURIComponent(request.title); + const hash = µb.selectedFilterLists.indexOf(request.location) !== -1 + ? '#subscribed' + : ''; + vAPI.tabs.open({ + url: `/asset-viewer.html?url=${url}&title=${title}&subscribe=1${hash}`, + select: true, + }); + break; + + default: + return vAPI.messaging.UNHANDLED; + } + + callback(response); +}; + +vAPI.messaging.listen({ + name: 'scriptlets', + listener: onMessage, +}); + +// <<<<< end of local scope +} + + +/******************************************************************************/ +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/pagestore.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/pagestore.js new file mode 100644 index 0000000..61b9d35 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/pagestore.js @@ -0,0 +1,1159 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +'use strict'; + +/******************************************************************************/ + +import contextMenu from './contextmenu.js'; +import logger from './logger.js'; +import staticNetFilteringEngine from './static-net-filtering.js'; +import µb from './background.js'; +import webext from './webext.js'; +import { orphanizeString } from './text-utils.js'; +import { redirectEngine } from './redirect-engine.js'; + +import { + sessionFirewall, + sessionSwitches, + sessionURLFiltering, +} from './filtering-engines.js'; + +import { + domainFromHostname, + hostnameFromURI, + isNetworkURI, +} from './uri-utils.js'; + +/******************************************************************************* + +A PageRequestStore object is used to store net requests in two ways: + +To record distinct net requests +To create a log of net requests + +**/ + +/******************************************************************************/ + +const NetFilteringResultCache = class { + constructor() { + this.init(); + } + + init() { + this.blocked = new Map(); + this.results = new Map(); + this.hash = 0; + this.timer = undefined; + return this; + } + + // https://github.com/gorhill/uBlock/issues/3619 + // Don't collapse redirected resources + rememberResult(fctxt, result) { + if ( fctxt.tabId <= 0 ) { return; } + if ( this.results.size === 0 ) { + this.pruneAsync(); + } + const key = `${fctxt.getDocHostname()} ${fctxt.type} ${fctxt.url}`; + this.results.set(key, { + result, + redirectURL: fctxt.redirectURL, + logData: fctxt.filter, + tstamp: Date.now() + }); + if ( result !== 1 || fctxt.redirectURL !== undefined ) { return; } + const now = Date.now(); + this.blocked.set(key, now); + this.hash = now; + } + + rememberBlock(fctxt) { + if ( fctxt.tabId <= 0 ) { return; } + if ( this.blocked.size === 0 ) { + this.pruneAsync(); + } + if ( fctxt.redirectURL !== undefined ) { return; } + const now = Date.now(); + this.blocked.set( + `${fctxt.getDocHostname()} ${fctxt.type} ${fctxt.url}`, + now + ); + this.hash = now; + } + + forgetResult(docHostname, type, url) { + const key = `${docHostname} ${type} ${url}`; + this.results.delete(key); + this.blocked.delete(key); + } + + empty() { + this.blocked.clear(); + this.results.clear(); + this.hash = 0; + if ( this.timer !== undefined ) { + clearTimeout(this.timer); + this.timer = undefined; + } + } + + prune() { + const obsolete = Date.now() - this.shelfLife; + for ( const entry of this.blocked ) { + if ( entry[1] <= obsolete ) { + this.results.delete(entry[0]); + this.blocked.delete(entry[0]); + } + } + for ( const entry of this.results ) { + if ( entry[1].tstamp <= obsolete ) { + this.results.delete(entry[0]); + } + } + if ( this.blocked.size !== 0 || this.results.size !== 0 ) { + this.pruneAsync(); + } + } + + pruneAsync() { + if ( this.timer !== undefined ) { return; } + this.timer = vAPI.setTimeout( + ( ) => { + this.timer = undefined; + this.prune(); + }, + this.shelfLife + ); + } + + lookupResult(fctxt) { + const entry = this.results.get( + fctxt.getDocHostname() + ' ' + + fctxt.type + ' ' + + fctxt.url + ); + if ( entry === undefined ) { return; } + // We need to use a new WAR secret if one is present since WAR secrets + // can only be used once. + if ( + entry.redirectURL !== undefined && + entry.redirectURL.startsWith(this.extensionOriginURL) + ) { + const redirectURL = new URL(entry.redirectURL); + redirectURL.searchParams.set('secret', vAPI.warSecret()); + entry.redirectURL = redirectURL.href; + } + return entry; + } + + lookupAllBlocked(hostname) { + const result = []; + for ( const entry of this.blocked ) { + const pos = entry[0].indexOf(' '); + if ( entry[0].slice(0, pos) === hostname ) { + result[result.length] = entry[0].slice(pos + 1); + } + } + return result; + } + + static factory() { + return new NetFilteringResultCache(); + } +}; + +NetFilteringResultCache.prototype.shelfLife = 15000; +NetFilteringResultCache.prototype.extensionOriginURL = vAPI.getURL('/'); + +/******************************************************************************/ + +// Frame stores are used solely to associate a URL with a frame id. + +const FrameStore = class { + constructor(frameURL, parentId) { + this.init(frameURL, parentId); + } + + init(frameURL, parentId) { + this.t0 = Date.now(); + this.parentId = parentId; + this.exceptCname = undefined; + this.clickToLoad = false; + this.rawURL = frameURL; + if ( frameURL !== undefined ) { + this.hostname = hostnameFromURI(frameURL); + this.domain = domainFromHostname(this.hostname) || this.hostname; + } + // Evaluated on-demand + // - 0b01: specific cosmetic filtering + // - 0b10: generic cosmetic filtering + this._cosmeticFilteringBits = undefined; + return this; + } + + dispose() { + this.rawURL = this.hostname = this.domain = ''; + if ( FrameStore.junkyard.length < FrameStore.junkyardMax ) { + FrameStore.junkyard.push(this); + } + return null; + } + + updateURL(url) { + if ( typeof url !== 'string' ) { return; } + this.rawURL = url; + this.hostname = hostnameFromURI(url); + this.domain = domainFromHostname(this.hostname) || this.hostname; + this._cosmeticFilteringBits = undefined; + } + + getCosmeticFilteringBits(tabId) { + if ( this._cosmeticFilteringBits !== undefined ) { + return this._cosmeticFilteringBits; + } + this._cosmeticFilteringBits = 0b11; + { + const result = staticNetFilteringEngine.matchRequestReverse( + 'specifichide', + this.rawURL + ); + if ( result !== 0 && logger.enabled ) { + µb.filteringContext + .duplicate() + .fromTabId(tabId) + .setURL(this.rawURL) + .setDocOriginFromURL(this.rawURL) + .setRealm('network') + .setType('specifichide') + .setFilter(staticNetFilteringEngine.toLogData()) + .toLogger(); + } + if ( result === 2 ) { + this._cosmeticFilteringBits &= ~0b01; + } + } + { + const result = staticNetFilteringEngine.matchRequestReverse( + 'generichide', + this.rawURL + ); + if ( result !== 0 && logger.enabled ) { + µb.filteringContext + .duplicate() + .fromTabId(tabId) + .setURL(this.rawURL) + .setDocOriginFromURL(this.rawURL) + .setRealm('network') + .setType('generichide') + .setFilter(staticNetFilteringEngine.toLogData()) + .toLogger(); + } + if ( result === 2 ) { + this._cosmeticFilteringBits &= ~0b10; + } + } + return this._cosmeticFilteringBits; + } + + shouldApplySpecificCosmeticFilters(tabId) { + return (this.getCosmeticFilteringBits(tabId) & 0b01) !== 0; + } + + shouldApplyGenericCosmeticFilters(tabId) { + return (this.getCosmeticFilteringBits(tabId) & 0b10) !== 0; + } + + static factory(frameURL, parentId = -1) { + const entry = FrameStore.junkyard.pop(); + if ( entry === undefined ) { + return new FrameStore(frameURL, parentId); + } + return entry.init(frameURL, parentId); + } +}; + +// To mitigate memory churning +FrameStore.junkyard = []; +FrameStore.junkyardMax = 50; + +/******************************************************************************/ + +const CountDetails = class { + constructor() { + this.allowed = { any: 0, frame: 0, script: 0 }; + this.blocked = { any: 0, frame: 0, script: 0 }; + } + reset() { + const { allowed, blocked } = this; + blocked.any = blocked.frame = blocked.script = + allowed.any = allowed.frame = allowed.script = 0; + } + inc(blocked, type = undefined) { + const stat = blocked ? this.blocked : this.allowed; + if ( type !== undefined ) { stat[type] += 1; } + stat.any += 1; + } +}; + +const HostnameDetails = class { + constructor(hostname) { + this.counts = new CountDetails(); + this.init(hostname); + } + init(hostname) { + this.hostname = hostname; + this.counts.reset(); + } + dispose() { + this.hostname = ''; + if ( HostnameDetails.junkyard.length < HostnameDetails.junkyardMax ) { + HostnameDetails.junkyard.push(this); + } + } +}; + +HostnameDetails.junkyard = []; +HostnameDetails.junkyardMax = 100; + +const HostnameDetailsMap = class extends Map { + reset() { + this.clear(); + } + dispose() { + for ( const item of this.values() ) { + item.dispose(); + } + this.reset(); + } +}; + +/******************************************************************************/ + +const PageStore = class { + constructor(tabId, details) { + this.extraData = new Map(); + this.journal = []; + this.journalTimer = undefined; + this.journalLastCommitted = this.journalLastUncommitted = -1; + this.journalLastUncommittedOrigin = undefined; + this.netFilteringCache = NetFilteringResultCache.factory(); + this.hostnameDetailsMap = new HostnameDetailsMap(); + this.counts = new CountDetails(); + this.init(tabId, details); + } + + static factory(tabId, details) { + let entry = PageStore.junkyard.pop(); + if ( entry === undefined ) { + entry = new PageStore(tabId, details); + } else { + entry.init(tabId, details); + } + return entry; + } + + // https://github.com/gorhill/uBlock/issues/3201 + // The context is used to determine whether we report behavior change + // to the logger. + + init(tabId, details) { + const tabContext = µb.tabContextManager.mustLookup(tabId); + this.tabId = tabId; + + // If we are navigating from-to same site, remember whether large + // media elements were temporarily allowed. + if ( + typeof this.allowLargeMediaElementsUntil !== 'number' || + tabContext.rootHostname !== this.tabHostname + ) { + this.allowLargeMediaElementsUntil = Date.now(); + } + + this.tabHostname = tabContext.rootHostname; + this.rawURL = tabContext.rawURL; + this.hostnameDetailsMap.reset(); + this.contentLastModified = 0; + this.logData = undefined; + this.counts.reset(); + this.remoteFontCount = 0; + this.popupBlockedCount = 0; + this.largeMediaCount = 0; + this.largeMediaTimer = null; + this.allowLargeMediaElementsRegex = undefined; + this.extraData.clear(); + + this.frameAddCount = 0; + this.frames = new Map(); + this.setFrameURL({ url: tabContext.rawURL }); + + if ( this.titleFromDetails(details) === false ) { + this.title = tabContext.rawURL; + } + + // Evaluated on-demand + this._noCosmeticFiltering = undefined; + + return this; + } + + reuse(context, details) { + // When force refreshing a page, the page store data needs to be reset. + + // If the hostname changes, we can't merely just update the context. + const tabContext = µb.tabContextManager.mustLookup(this.tabId); + if ( tabContext.rootHostname !== this.tabHostname ) { + context = ''; + } + + // If URL changes without a page reload (more and more common), then + // we need to keep all that we collected for reuse. In particular, + // not doing so was causing a problem in `videos.foxnews.com`: + // clicking a video thumbnail would not work, because the frame + // hierarchy structure was flushed from memory, while not really being + // flushed on the page. + if ( context === 'tabUpdated' ) { + // As part of https://github.com/chrisaljoudi/uBlock/issues/405 + // URL changed, force a re-evaluation of filtering switch + this.rawURL = tabContext.rawURL; + this.setFrameURL({ url: this.rawURL }); + this.titleFromDetails(details); + return this; + } + + // A new page is completely reloaded from scratch, reset all. + if ( this.largeMediaTimer !== null ) { + clearTimeout(this.largeMediaTimer); + this.largeMediaTimer = null; + } + this.disposeFrameStores(); + this.init(this.tabId, details); + return this; + } + + dispose() { + this.tabHostname = ''; + this.title = ''; + this.rawURL = ''; + this.hostnameDetailsMap.dispose(); + this.netFilteringCache.empty(); + this.allowLargeMediaElementsUntil = Date.now(); + this.allowLargeMediaElementsRegex = undefined; + if ( this.largeMediaTimer !== null ) { + clearTimeout(this.largeMediaTimer); + this.largeMediaTimer = null; + } + this.disposeFrameStores(); + if ( this.journalTimer !== undefined ) { + clearTimeout(this.journalTimer); + this.journalTimer = undefined; + } + this.journal = []; + this.journalLastUncommittedOrigin = undefined; + this.journalLastCommitted = this.journalLastUncommitted = -1; + if ( PageStore.junkyard.length < PageStore.junkyardMax ) { + PageStore.junkyard.push(this); + } + return null; + } + + titleFromDetails(details) { + if ( + details instanceof Object === false || + details.title === undefined + ) { + return false; + } + this.title = orphanizeString(details.title.slice(0, 128)); + return true; + } + + disposeFrameStores() { + for ( const frameStore of this.frames.values() ) { + frameStore.dispose(); + } + this.frames.clear(); + } + + getFrameStore(frameId) { + return this.frames.get(frameId) || null; + } + + // https://github.com/uBlockOrigin/uBlock-issues/issues/1858 + // Mind that setFrameURL() can be called from navigation event handlers. + setFrameURL(details) { + let { frameId, url, parentFrameId } = details; + if ( frameId === undefined ) { frameId = 0; } + if ( parentFrameId === undefined ) { parentFrameId = -1; } + let frameStore = this.frames.get(frameId); + if ( frameStore !== undefined ) { + if ( url === frameStore.rawURL ) { + frameStore.parentId = parentFrameId; + } else { + frameStore.init(url, parentFrameId); + } + return frameStore; + } + frameStore = FrameStore.factory(url, parentFrameId); + this.frames.set(frameId, frameStore); + this.frameAddCount += 1; + if ( url.startsWith('about:') ) { + frameStore.updateURL(this.getEffectiveFrameURL({ frameId })); + } + if ( (this.frameAddCount & 0b111111) === 0 ) { + this.pruneFrames(); + } + return frameStore; + } + + getEffectiveFrameURL(sender) { + let { frameId } = sender; + for (;;) { + const frameStore = this.getFrameStore(frameId); + if ( frameStore === null ) { break; } + if ( frameStore.rawURL.startsWith('about:') === false ) { + return frameStore.rawURL; + } + frameId = frameStore.parentId; + if ( frameId === -1 ) { break; } + } + return sender.frameURL; + } + + // There is no event to tell us a specific subframe has been removed from + // the main document. The code below will remove subframes which are no + // longer present in the root document. Removing obsolete subframes is + // not a critical task, so this is executed just once on a while, to avoid + // bloated dictionary of subframes. + // A TTL is used to avoid race conditions when new iframes are added + // through the webRequest API but still not yet visible through the + // webNavigation API. + async pruneFrames() { + let entries; + try { + entries = await webext.webNavigation.getAllFrames({ + tabId: this.tabId + }); + } catch(ex) { + } + if ( Array.isArray(entries) === false ) { return; } + const toKeep = new Set(); + for ( const { frameId } of entries ) { + toKeep.add(frameId); + } + const obsolete = Date.now() - 60000; + for ( const [ frameId, { t0 } ] of this.frames ) { + if ( toKeep.has(frameId) || t0 >= obsolete ) { continue; } + this.frames.delete(frameId); + } + } + + getNetFilteringSwitch() { + return µb.tabContextManager + .mustLookup(this.tabId) + .getNetFilteringSwitch(); + } + + toggleNetFilteringSwitch(url, scope, state) { + µb.toggleNetFilteringSwitch(url, scope, state); + this.netFilteringCache.empty(); + } + + shouldApplyCosmeticFilters(frameId = 0) { + if ( this._noCosmeticFiltering === undefined ) { + this._noCosmeticFiltering = this.getNetFilteringSwitch() === false; + if ( this._noCosmeticFiltering === false ) { + this._noCosmeticFiltering = sessionSwitches.evaluateZ( + 'no-cosmetic-filtering', + this.tabHostname + ) === true; + if ( this._noCosmeticFiltering && logger.enabled ) { + µb.filteringContext + .duplicate() + .fromTabId(this.tabId) + .setURL(this.rawURL) + .setRealm('cosmetic') + .setType('dom') + .setFilter(sessionSwitches.toLogData()) + .toLogger(); + } + } + } + if ( this._noCosmeticFiltering ) { return false; } + if ( frameId === -1 ) { return true; } + // Cosmetic filtering can be effectively disabled when both specific + // and generic cosmetic filters are disabled. + return this.shouldApplySpecificCosmeticFilters(frameId) || + this.shouldApplyGenericCosmeticFilters(frameId); + } + + shouldApplySpecificCosmeticFilters(frameId) { + if ( this.shouldApplyCosmeticFilters(-1) === false ) { return false; } + const frameStore = this.getFrameStore(frameId); + if ( frameStore === null ) { return false; } + return frameStore.shouldApplySpecificCosmeticFilters(this.tabId); + } + + shouldApplyGenericCosmeticFilters(frameId) { + if ( this.shouldApplyCosmeticFilters(-1) === false ) { return false; } + const frameStore = this.getFrameStore(frameId); + if ( frameStore === null ) { return false; } + return frameStore.shouldApplyGenericCosmeticFilters(this.tabId); + } + + // https://github.com/gorhill/uBlock/issues/2105 + // Be sure to always include the current page's hostname -- it might not + // be present when the page itself is pulled from the browser's + // short-term memory cache. + getAllHostnameDetails() { + if ( + this.hostnameDetailsMap.has(this.tabHostname) === false && + isNetworkURI(this.rawURL) + ) { + this.hostnameDetailsMap.set( + this.tabHostname, + new HostnameDetails(this.tabHostname) + ); + } + return this.hostnameDetailsMap; + } + + injectLargeMediaElementScriptlet() { + vAPI.tabs.executeScript(this.tabId, { + file: '/js/scriptlets/load-large-media-interactive.js', + allFrames: true, + runAt: 'document_idle', + }); + contextMenu.update(this.tabId); + } + + temporarilyAllowLargeMediaElements(state) { + this.largeMediaCount = 0; + contextMenu.update(this.tabId); + if ( state ) { + this.allowLargeMediaElementsUntil = 0; + this.allowLargeMediaElementsRegex = undefined; + } else { + this.allowLargeMediaElementsUntil = Date.now(); + } + vAPI.tabs.executeScript(this.tabId, { + file: '/js/scriptlets/load-large-media-all.js', + allFrames: true, + }); + } + + // https://github.com/gorhill/uBlock/issues/2053 + // There is no way around using journaling to ensure we deal properly with + // potentially out of order navigation events vs. network request events. + journalAddRequest(fctxt, result) { + const hostname = fctxt.getHostname(); + if ( hostname === '' ) { return; } + this.journal.push(hostname, result, fctxt.itype); + if ( this.journalTimer !== undefined ) { return; } + this.journalTimer = vAPI.setTimeout( + ( ) => { this.journalProcess(true); }, + µb.hiddenSettings.requestJournalProcessPeriod + ); + } + + journalAddRootFrame(type, url) { + if ( type === 'committed' ) { + this.journalLastCommitted = this.journal.length; + if ( + this.journalLastUncommitted !== -1 && + this.journalLastUncommitted < this.journalLastCommitted && + this.journalLastUncommittedOrigin === hostnameFromURI(url) + ) { + this.journalLastCommitted = this.journalLastUncommitted; + } + } else if ( type === 'uncommitted' ) { + const newOrigin = hostnameFromURI(url); + if ( + this.journalLastUncommitted === -1 || + this.journalLastUncommittedOrigin !== newOrigin + ) { + this.journalLastUncommitted = this.journal.length; + this.journalLastUncommittedOrigin = newOrigin; + } + } + if ( this.journalTimer !== undefined ) { + clearTimeout(this.journalTimer); + } + this.journalTimer = vAPI.setTimeout( + ( ) => { this.journalProcess(true); }, + µb.hiddenSettings.requestJournalProcessPeriod + ); + } + + journalProcess(fromTimer = false) { + if ( fromTimer === false ) { clearTimeout(this.journalTimer); } + this.journalTimer = undefined; + + const journal = this.journal; + const pivot = Math.max(0, this.journalLastCommitted); + const now = Date.now(); + const { SCRIPT, SUB_FRAME } = µb.FilteringContext; + let aggregateAllowed = 0; + let aggregateBlocked = 0; + + // Everything after pivot originates from current page. + for ( let i = pivot; i < journal.length; i += 3 ) { + const hostname = journal[i+0]; + let hnDetails = this.hostnameDetailsMap.get(hostname); + if ( hnDetails === undefined ) { + hnDetails = new HostnameDetails(hostname); + this.hostnameDetailsMap.set(hostname, hnDetails); + this.contentLastModified = now; + } + const blocked = journal[i+1] === 1; + const itype = journal[i+2]; + if ( itype === SCRIPT ) { + hnDetails.counts.inc(blocked, 'script'); + this.counts.inc(blocked, 'script'); + } else if ( itype === SUB_FRAME ) { + hnDetails.counts.inc(blocked, 'frame'); + this.counts.inc(blocked, 'frame'); + } else { + hnDetails.counts.inc(blocked); + this.counts.inc(blocked); + } + if ( blocked ) { + aggregateBlocked += 1; + } else { + aggregateAllowed += 1; + } + } + this.journalLastUncommitted = this.journalLastCommitted = -1; + + // https://github.com/chrisaljoudi/uBlock/issues/905#issuecomment-76543649 + // No point updating the badge if it's not being displayed. + if ( aggregateBlocked !== 0 && µb.userSettings.showIconBadge ) { + µb.updateToolbarIcon(this.tabId, 0x02); + } + + // Everything before pivot does not originate from current page -- we + // still need to bump global blocked/allowed counts. + for ( let i = 0; i < pivot; i += 3 ) { + if ( journal[i+1] === 1 ) { + aggregateBlocked += 1; + } else { + aggregateAllowed += 1; + } + } + if ( aggregateAllowed !== 0 || aggregateBlocked !== 0 ) { + µb.localSettings.blockedRequestCount += aggregateBlocked; + µb.localSettings.allowedRequestCount += aggregateAllowed; + µb.localSettingsLastModified = now; + } + journal.length = 0; + } + + filterRequest(fctxt) { + fctxt.filter = undefined; + fctxt.redirectURL = undefined; + + if ( this.getNetFilteringSwitch(fctxt) === false ) { + return 0; + } + + if ( + fctxt.itype === fctxt.CSP_REPORT && + this.filterCSPReport(fctxt) === 1 + ) { + return 1; + } + + if ( + (fctxt.itype & fctxt.FONT_ANY) !== 0 && + this.filterFont(fctxt) === 1 ) + { + return 1; + } + + if ( + fctxt.itype === fctxt.SCRIPT && + this.filterScripting(fctxt, true) === 1 + ) { + return 1; + } + + const cacheableResult = + this.cacheableResults.has(fctxt.itype) && + fctxt.aliasURL === undefined; + + if ( cacheableResult ) { + const entry = this.netFilteringCache.lookupResult(fctxt); + if ( entry !== undefined ) { + fctxt.redirectURL = entry.redirectURL; + fctxt.filter = entry.logData; + return entry.result; + } + } + + const requestType = fctxt.type; + const loggerEnabled = logger.enabled; + + // Dynamic URL filtering. + let result = sessionURLFiltering.evaluateZ( + fctxt.getTabHostname(), + fctxt.url, + requestType + ); + if ( result !== 0 && loggerEnabled ) { + fctxt.filter = sessionURLFiltering.toLogData(); + } + + // Dynamic hostname/type filtering. + if ( result === 0 && µb.userSettings.advancedUserEnabled ) { + result = sessionFirewall.evaluateCellZY( + fctxt.getTabHostname(), + fctxt.getHostname(), + requestType + ); + if ( result !== 0 && result !== 3 && loggerEnabled ) { + fctxt.filter = sessionFirewall.toLogData(); + } + } + + // Static filtering has lowest precedence. + const snfe = staticNetFilteringEngine; + if ( result === 0 || result === 3 ) { + result = snfe.matchRequest(fctxt); + if ( result !== 0 ) { + if ( loggerEnabled ) { + fctxt.setFilter(snfe.toLogData()); + } + // https://github.com/uBlockOrigin/uBlock-issues/issues/943 + // Blanket-except blocked aliased canonical hostnames? + if ( + result === 1 && + fctxt.aliasURL !== undefined && + snfe.isBlockImportant() === false && + this.shouldExceptCname(fctxt) + ) { + return 2; + } + } + } + + // Click-to-load? + // When frameId is not -1, the resource is always sub_frame. + if ( result === 1 && fctxt.frameId !== -1 ) { + const frameStore = this.getFrameStore(fctxt.frameId); + if ( frameStore !== null && frameStore.clickToLoad ) { + result = 2; + if ( loggerEnabled ) { + fctxt.pushFilter({ + result, + source: 'network', + raw: 'click-to-load', + }); + } + } + } + + // Modifier(s)? + // A modifier is an action which transform the original network request. + // https://github.com/gorhill/uBlock/issues/949 + // Redirect blocked request? + // https://github.com/uBlockOrigin/uBlock-issues/issues/760 + // Redirect non-blocked request? + if ( (fctxt.itype & fctxt.INLINE_ANY) === 0 ) { + if ( result === 1 ) { + this.redirectBlockedRequest(fctxt); + } else if ( snfe.hasQuery(fctxt) ) { + this.redirectNonBlockedRequest(fctxt); + } + } + + if ( cacheableResult ) { + this.netFilteringCache.rememberResult(fctxt, result); + } else if ( result === 1 && this.collapsibleResources.has(fctxt.itype) ) { + this.netFilteringCache.rememberBlock(fctxt); + } + + return result; + } + + filterOnHeaders(fctxt, headers) { + fctxt.filter = undefined; + + if ( this.getNetFilteringSwitch(fctxt) === false ) { return 0; } + + let result = staticNetFilteringEngine.matchHeaders(fctxt, headers); + if ( result === 0 ) { return 0; } + + const loggerEnabled = logger.enabled; + if ( loggerEnabled ) { + fctxt.filter = staticNetFilteringEngine.toLogData(); + } + + // Dynamic filtering allow rules + // URL filtering + if ( + result === 1 && + sessionURLFiltering.evaluateZ( + fctxt.getTabHostname(), + fctxt.url, + fctxt.type + ) === 2 + ) { + result = 2; + if ( loggerEnabled ) { + fctxt.filter = sessionURLFiltering.toLogData(); + } + } + // Hostname filtering + if ( + result === 1 && + µb.userSettings.advancedUserEnabled && + sessionFirewall.evaluateCellZY( + fctxt.getTabHostname(), + fctxt.getHostname(), + fctxt.type + ) === 2 + ) { + result = 2; + if ( loggerEnabled ) { + fctxt.filter = sessionFirewall.toLogData(); + } + } + + return result; + } + + redirectBlockedRequest(fctxt) { + const directives = staticNetFilteringEngine.redirectRequest( + redirectEngine, + fctxt + ); + if ( directives === undefined ) { return; } + if ( logger.enabled !== true ) { return; } + fctxt.pushFilters(directives.map(a => a.logData())); + if ( fctxt.redirectURL === undefined ) { return; } + fctxt.pushFilter({ + source: 'redirect', + raw: redirectEngine.resourceNameRegister + }); + } + + redirectNonBlockedRequest(fctxt) { + const directives = staticNetFilteringEngine.filterQuery(fctxt); + if ( directives === undefined ) { return; } + if ( logger.enabled !== true ) { return; } + fctxt.pushFilters(directives.map(a => a.logData())); + if ( fctxt.redirectURL === undefined ) { return; } + fctxt.pushFilter({ + source: 'redirect', + raw: fctxt.redirectURL + }); + } + + filterCSPReport(fctxt) { + if ( + sessionSwitches.evaluateZ( + 'no-csp-reports', + fctxt.getHostname() + ) + ) { + if ( logger.enabled ) { + fctxt.filter = sessionSwitches.toLogData(); + } + return 1; + } + return 0; + } + + filterFont(fctxt) { + if ( fctxt.itype === fctxt.FONT ) { + this.remoteFontCount += 1; + } + if ( + sessionSwitches.evaluateZ( + 'no-remote-fonts', + fctxt.getTabHostname() + ) !== false + ) { + if ( logger.enabled ) { + fctxt.filter = sessionSwitches.toLogData(); + } + return 1; + } + return 0; + } + + filterScripting(fctxt, netFiltering) { + fctxt.filter = undefined; + if ( netFiltering === undefined ) { + netFiltering = this.getNetFilteringSwitch(fctxt); + } + if ( + netFiltering === false || + sessionSwitches.evaluateZ( + 'no-scripting', + fctxt.getTabHostname() + ) === false + ) { + return 0; + } + if ( logger.enabled ) { + fctxt.filter = sessionSwitches.toLogData(); + } + return 1; + } + + // The caller is responsible to check whether filtering is enabled or not. + filterLargeMediaElement(fctxt, size) { + fctxt.filter = undefined; + + if ( this.allowLargeMediaElementsUntil === 0 ) { + return 0; + } + // Disregard large media elements previously allowed: for example, to + // seek inside a previously allowed audio/video. + if ( + this.allowLargeMediaElementsRegex instanceof RegExp && + this.allowLargeMediaElementsRegex.test(fctxt.url) + ) { + return 0; + } + if ( Date.now() < this.allowLargeMediaElementsUntil ) { + const sources = this.allowLargeMediaElementsRegex instanceof RegExp + ? [ this.allowLargeMediaElementsRegex.source ] + : []; + sources.push('^' + µb.escapeRegex(fctxt.url)); + this.allowLargeMediaElementsRegex = new RegExp(sources.join('|')); + return 0; + } + if ( + sessionSwitches.evaluateZ( + 'no-large-media', + fctxt.getTabHostname() + ) !== true + ) { + this.allowLargeMediaElementsUntil = 0; + return 0; + } + if ( (size >>> 10) < µb.userSettings.largeMediaSize ) { + return 0; + } + + this.largeMediaCount += 1; + if ( this.largeMediaTimer === null ) { + this.largeMediaTimer = vAPI.setTimeout(( ) => { + this.largeMediaTimer = null; + this.injectLargeMediaElementScriptlet(); + }, 500); + } + + if ( logger.enabled ) { + fctxt.filter = sessionSwitches.toLogData(); + } + + return 1; + } + + clickToLoad(frameId, frameURL) { + let frameStore = this.getFrameStore(frameId); + if ( frameStore === null ) { + frameStore = this.setFrameURL({ frameId, url: frameURL }); + } + this.netFilteringCache.forgetResult( + this.tabHostname, + 'sub_frame', + frameURL + ); + frameStore.clickToLoad = true; + } + + shouldExceptCname(fctxt) { + let exceptCname; + let frameStore; + if ( fctxt.docId !== undefined ) { + frameStore = this.getFrameStore(fctxt.docId); + if ( frameStore instanceof Object ) { + exceptCname = frameStore.exceptCname; + } + } + if ( exceptCname === undefined ) { + const result = staticNetFilteringEngine.matchRequestReverse( + 'cname', + frameStore instanceof Object + ? frameStore.rawURL + : fctxt.getDocOrigin() + ); + exceptCname = result === 2 + ? staticNetFilteringEngine.toLogData() + : false; + if ( frameStore instanceof Object ) { + frameStore.exceptCname = exceptCname; + } + } + if ( exceptCname === false ) { return false; } + if ( exceptCname instanceof Object ) { + fctxt.setFilter(exceptCname); + } + return true; + } + + getBlockedResources(request, response) { + const normalURL = µb.normalizeTabURL(this.tabId, request.frameURL); + const resources = request.resources; + const fctxt = µb.filteringContext; + fctxt.fromTabId(this.tabId) + .setDocOriginFromURL(normalURL); + // Force some resources to go through the filtering engine in order to + // populate the blocked-resources cache. This is required because for + // some resources it's not possible to detect whether they were blocked + // content script-side (i.e. `iframes` -- unlike `img`). + if ( Array.isArray(resources) && resources.length !== 0 ) { + for ( const resource of resources ) { + this.filterRequest( + fctxt.setType(resource.type).setURL(resource.url) + ); + } + } + if ( this.netFilteringCache.hash === response.hash ) { return; } + response.hash = this.netFilteringCache.hash; + response.blockedResources = + this.netFilteringCache.lookupAllBlocked(fctxt.getDocHostname()); + } +}; + +PageStore.prototype.cacheableResults = new Set([ + µb.FilteringContext.SUB_FRAME, +]); + +PageStore.prototype.collapsibleResources = new Set([ + µb.FilteringContext.IMAGE, + µb.FilteringContext.MEDIA, + µb.FilteringContext.OBJECT, + µb.FilteringContext.SUB_FRAME, +]); + +// To mitigate memory churning +PageStore.junkyard = []; +PageStore.junkyardMax = 10; + +/******************************************************************************/ + +export { PageStore }; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/popup-fenix.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/popup-fenix.js new file mode 100644 index 0000000..9d8e002 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/popup-fenix.js @@ -0,0 +1,1447 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +/* global uDom */ + +'use strict'; + +import punycode from '../lib/punycode.js'; + +/******************************************************************************/ + +let popupFontSize = 'unset'; +vAPI.localStorage.getItemAsync('popupFontSize').then(value => { + if ( typeof value !== 'string' || value === 'unset' ) { return; } + document.body.style.setProperty('--font-size', value); + popupFontSize = value; +}); + +// https://github.com/chrisaljoudi/uBlock/issues/996 +// Experimental: mitigate glitchy popup UI: immediately set the firewall +// pane visibility to its last known state. By default the pane is hidden. +vAPI.localStorage.getItemAsync('popupPanelSections').then(bits => { + if ( typeof bits !== 'number' ) { return; } + sectionBitsToAttribute(bits); +}); + +/******************************************************************************/ + +const messaging = vAPI.messaging; +const scopeToSrcHostnameMap = { + '/': '*', + '.': '' +}; +const hostnameToSortableTokenMap = new Map(); +const statsStr = vAPI.i18n('popupBlockedStats'); +const domainsHitStr = vAPI.i18n('popupHitDomainCount'); + +let popupData = {}; +let dfPaneBuilt = false; +let dfHotspots = null; +const allHostnameRows = []; +let cachedPopupHash = ''; + +// https://github.com/gorhill/uBlock/issues/2550 +// Solution inspired from +// - https://bugs.chromium.org/p/chromium/issues/detail?id=683314 +// - https://bugzilla.mozilla.org/show_bug.cgi?id=1332714#c17 +// Confusable character set from: +// - http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%D0%B0%D1%81%D4%81%D0%B5%D2%BB%D1%96%D1%98%D3%8F%D0%BE%D1%80%D4%9B%D1%95%D4%9D%D1%85%D1%83%D1%8A%D0%AC%D2%BD%D0%BF%D0%B3%D1%B5%D1%A1%5D&g=gc&i= +// Linked from: +// - https://www.chromium.org/developers/design-documents/idn-in-google-chrome +const reCyrillicNonAmbiguous = /[\u0400-\u042b\u042d-\u042f\u0431\u0432\u0434\u0436-\u043d\u0442\u0444\u0446-\u0449\u044b-\u0454\u0457\u0459-\u0460\u0462-\u0474\u0476-\u04ba\u04bc\u04be-\u04ce\u04d0-\u0500\u0502-\u051a\u051c\u051e-\u052f]/; +const reCyrillicAmbiguous = /[\u042c\u0430\u0433\u0435\u043e\u043f\u0440\u0441\u0443\u0445\u044a\u0455\u0456\u0458\u0461\u0475\u04bb\u04bd\u04cf\u0501\u051b\u051d]/; + +/******************************************************************************/ + +const cachePopupData = function(data) { + popupData = {}; + scopeToSrcHostnameMap['.'] = ''; + hostnameToSortableTokenMap.clear(); + + if ( typeof data !== 'object' ) { + return popupData; + } + popupData = data; + popupData.cnameMap = new Map(popupData.cnameMap); + scopeToSrcHostnameMap['.'] = popupData.pageHostname || ''; + const hostnameDict = popupData.hostnameDict; + if ( typeof hostnameDict !== 'object' ) { + return popupData; + } + for ( const hostname in hostnameDict ) { + if ( hostnameDict.hasOwnProperty(hostname) === false ) { continue; } + let domain = hostnameDict[hostname].domain; + let prefix = hostname.slice(0, 0 - domain.length - 1); + // Prefix with space char for 1st-party hostnames: this ensure these + // will come first in list. + if ( domain === popupData.pageDomain ) { + domain = '\u0020'; + } + hostnameToSortableTokenMap.set( + hostname, + domain + ' ' + prefix.split('.').reverse().join('.') + ); + } + return popupData; +}; + +/******************************************************************************/ + +const hashFromPopupData = function(reset = false) { + // It makes no sense to offer to refresh the behind-the-scene scope + if ( popupData.pageHostname === 'behind-the-scene' ) { + document.body.classList.remove('needReload'); + return; + } + + const hasher = []; + const rules = popupData.firewallRules; + for ( const key in rules ) { + const rule = rules[key]; + if ( rule === undefined ) { continue; } + hasher.push(rule); + } + hasher.sort(); + hasher.push(uDom('body').hasClass('off')); + hasher.push(uDom.nodeFromId('no-large-media').classList.contains('on')); + hasher.push(uDom.nodeFromId('no-cosmetic-filtering').classList.contains('on')); + hasher.push(uDom.nodeFromId('no-remote-fonts').classList.contains('on')); + hasher.push(uDom.nodeFromId('no-scripting').classList.contains('on')); + + const hash = hasher.join(''); + if ( reset ) { + cachedPopupHash = hash; + } + document.body.classList.toggle('needReload', hash !== cachedPopupHash); +}; + +/******************************************************************************/ + +// greater-than-zero test + +const gtz = n => typeof n === 'number' && n > 0; + +/******************************************************************************/ + +const formatNumber = function(count) { + if ( typeof count !== 'number' ) { return ''; } + if ( count < 1e6 ) { return count.toLocaleString(); } + + if ( + intlNumberFormat === undefined && + Intl.NumberFormat instanceof Function + ) { + const intl = new Intl.NumberFormat(undefined, { + notation: 'compact', + maximumSignificantDigits: 4 + }); + if ( + intl.resolvedOptions instanceof Function && + intl.resolvedOptions().hasOwnProperty('notation') + ) { + intlNumberFormat = intl; + } + } + + if ( intlNumberFormat ) { + return intlNumberFormat.format(count); + } + + // https://github.com/uBlockOrigin/uBlock-issues/issues/1027#issuecomment-629696676 + // For platforms which do not support proper number formatting, use + // a poor's man compact form, which unfortunately is not i18n-friendly. + count /= 1000000; + if ( count >= 100 ) { + count = Math.floor(count * 10) / 10; + } else if ( count > 10 ) { + count = Math.floor(count * 100) / 100; + } else { + count = Math.floor(count * 1000) / 1000; + } + return (count).toLocaleString(undefined) + '\u2009M'; +}; + +let intlNumberFormat; + +/******************************************************************************/ + +const safePunycodeToUnicode = function(hn) { + const pretty = punycode.toUnicode(hn); + return pretty === hn || + reCyrillicAmbiguous.test(pretty) === false || + reCyrillicNonAmbiguous.test(pretty) + ? pretty + : hn; +}; + +/******************************************************************************/ + +const updateFirewallCellCount = function(cells, allowed, blocked) { + for ( const cell of cells ) { + if ( gtz(allowed) ) { + cell.setAttribute( + 'data-acount', + Math.min(Math.ceil(Math.log(allowed + 1) / Math.LN10), 3) + ); + } else { + cell.setAttribute('data-acount', '0'); + } + if ( gtz(blocked) ) { + cell.setAttribute( + 'data-bcount', + Math.min(Math.ceil(Math.log(blocked + 1) / Math.LN10), 3) + ); + } else { + cell.setAttribute('data-bcount', '0'); + } + } +}; + +/******************************************************************************/ + +const updateFirewallCellRule = function(cells, scope, des, type, rule) { + const ruleParts = rule !== undefined ? rule.split(' ') : undefined; + + for ( const cell of cells ) { + if ( ruleParts === undefined ) { + cell.removeAttribute('class'); + continue; + } + + const action = updateFirewallCellRule.actionNames[ruleParts[3]]; + cell.setAttribute('class', `${action}Rule`); + + // Use dark shade visual cue if the rule is specific to the cell. + if ( + (ruleParts[1] !== '*' || ruleParts[2] === type) && + (ruleParts[1] === des) && + (ruleParts[0] === scopeToSrcHostnameMap[scope]) + + ) { + cell.classList.add('ownRule'); + } + } +}; + +updateFirewallCellRule.actionNames = { '1': 'block', '2': 'allow', '3': 'noop' }; + +/******************************************************************************/ + +const updateAllFirewallCells = function(doRules = true, doCounts = true) { + const { pageDomain } = popupData; + const rowContainer = document.getElementById('firewall'); + const rows = rowContainer.querySelectorAll('#firewall > [data-des][data-type]'); + + let a1pScript = 0, b1pScript = 0; + let a3pScript = 0, b3pScript = 0; + let a3pFrame = 0, b3pFrame = 0; + + for ( const row of rows ) { + const des = row.getAttribute('data-des'); + const type = row.getAttribute('data-type'); + if ( doRules ) { + updateFirewallCellRule( + row.querySelectorAll(`:scope > span[data-src="/"]`), + '/', + des, + type, + popupData.firewallRules[`/ ${des} ${type}`] + ); + } + const cells = row.querySelectorAll(`:scope > span[data-src="."]`); + if ( doRules ) { + updateFirewallCellRule( + cells, + '.', + des, + type, + popupData.firewallRules[`. ${des} ${type}`] + ); + } + if ( des === '*' || type !== '*' ) { continue; } + if ( doCounts === false ) { continue; } + const hnDetails = popupData.hostnameDict[des]; + if ( hnDetails === undefined ) { + updateFirewallCellCount(cells); + continue; + } + const { allowed, blocked } = hnDetails.counts; + updateFirewallCellCount([ cells[0] ], allowed.any, blocked.any); + const { totals } = hnDetails; + if ( totals !== undefined ) { + updateFirewallCellCount([ cells[1] ], totals.allowed.any, totals.blocked.any); + } + if ( hnDetails.domain === pageDomain ) { + a1pScript += allowed.script; b1pScript += blocked.script; + } else { + a3pScript += allowed.script; b3pScript += blocked.script; + a3pFrame += allowed.frame; b3pFrame += blocked.frame; + } + } + + if ( doCounts ) { + const fromType = type => + document.querySelectorAll( + `#firewall > [data-des="*"][data-type="${type}"] > [data-src="."]` + ); + updateFirewallCellCount(fromType('1p-script'), a1pScript, b1pScript); + updateFirewallCellCount(fromType('3p-script'), a3pScript, b3pScript); + rowContainer.classList.toggle('has3pScript', a3pScript !== 0 || b3pScript !== 0); + updateFirewallCellCount(fromType('3p-frame'), a3pFrame, b3pFrame); + rowContainer.classList.toggle('has3pFrame', a3pFrame !== 0 || b3pFrame !== 0); + } + + document.body.classList.toggle('needSave', popupData.matrixIsDirty === true); +}; + +/******************************************************************************/ + +// Compute statistics useful only to firewall entries -- we need to call +// this only when overview pane needs to be rendered. + +const expandHostnameStats = ( ) => { + let dnDetails; + for ( const des of allHostnameRows ) { + const hnDetails = popupData.hostnameDict[des]; + const { domain, counts } = hnDetails; + const isDomain = des === domain; + const { allowed: hnAllowed, blocked: hnBlocked } = counts; + if ( isDomain ) { + dnDetails = hnDetails; + dnDetails.totals = JSON.parse(JSON.stringify(dnDetails.counts)); + } else { + const { allowed: dnAllowed, blocked: dnBlocked } = dnDetails.totals; + dnAllowed.any += hnAllowed.any; + dnBlocked.any += hnBlocked.any; + } + hnDetails.hasScript = hnAllowed.script !== 0 || hnBlocked.script !== 0; + dnDetails.hasScript = dnDetails.hasScript || hnDetails.hasScript; + hnDetails.hasFrame = hnAllowed.frame !== 0 || hnBlocked.frame !== 0; + dnDetails.hasFrame = dnDetails.hasFrame || hnDetails.hasFrame; + } +}; + +/******************************************************************************/ + +const buildAllFirewallRows = function() { + // Do this before removing the rows + if ( dfHotspots === null ) { + dfHotspots = uDom.nodeFromId('actionSelector'); + dfHotspots.addEventListener('click', setFirewallRuleHandler); + } + dfHotspots.remove(); + + // This must be called before we create the rows. + expandHostnameStats(); + + // Update incrementally: reuse existing rows if possible. + const rowContainer = document.getElementById('firewall'); + const toAppend = document.createDocumentFragment(); + const rowTemplate = document.querySelector( + '#templates > div[data-des=""][data-type="*"]' + ); + const { cnameMap, hostnameDict, pageDomain, pageHostname } = popupData; + + let row = rowContainer.querySelector( + 'div[data-des="*"][data-type="3p-frame"] + div' + ); + + for ( const des of allHostnameRows ) { + if ( row === null ) { + row = rowTemplate.cloneNode(true); + toAppend.appendChild(row); + } + row.setAttribute('data-des', des); + + const hnDetails = hostnameDict[des] || {}; + const isDomain = des === hnDetails.domain; + const prettyDomainName = des.includes('xn--') + ? punycode.toUnicode(des) + : des; + const isPunycoded = prettyDomainName !== des; + + if ( isDomain && row.childElementCount < 4 ) { + row.append(row.children[2].cloneNode(true)); + } else if ( isDomain === false && row.childElementCount === 4 ) { + row.children[3].remove(); + } + + const span = row.querySelector('span:first-of-type'); + span.querySelector(':scope > span > span').textContent = prettyDomainName; + + const classList = row.classList; + + let desExtra = ''; + if ( classList.toggle('isCname', cnameMap.has(des)) ) { + desExtra = punycode.toUnicode(cnameMap.get(des)); + } else if ( + isDomain && isPunycoded && + reCyrillicAmbiguous.test(prettyDomainName) && + reCyrillicNonAmbiguous.test(prettyDomainName) === false + ) { + desExtra = des; + } + span.querySelector('sub').textContent = desExtra; + + classList.toggle('isRootContext', des === pageHostname); + classList.toggle('is3p', hnDetails.domain !== pageDomain); + classList.toggle('isDomain', isDomain); + classList.toggle('hasSubdomains', isDomain && hnDetails.hasSubdomains); + classList.toggle('isSubdomain', !isDomain); + const { counts } = hnDetails; + classList.toggle('allowed', gtz(counts.allowed.any)); + classList.toggle('blocked', gtz(counts.blocked.any)); + const { totals } = hnDetails; + classList.toggle('totalAllowed', gtz(totals && totals.allowed.any)); + classList.toggle('totalBlocked', gtz(totals && totals.blocked.any)); + classList.toggle('hasScript', hnDetails.hasScript === true); + classList.toggle('hasFrame', hnDetails.hasFrame === true); + classList.toggle('expandException', expandExceptions.has(hnDetails.domain)); + + row = row.nextElementSibling; + } + + // Remove unused trailing rows + if ( row !== null ) { + while ( row.nextElementSibling !== null ) { + row.nextElementSibling.remove(); + } + row.remove(); + } + + // Add new rows all at once + if ( toAppend.childElementCount !== 0 ) { + rowContainer.append(toAppend); + } + + if ( dfPaneBuilt !== true && popupData.advancedUserEnabled ) { + uDom('#firewall') + .on('click', 'span[data-src]', unsetFirewallRuleHandler) + .on('mouseenter', '[data-src]', mouseenterCellHandler) + .on('mouseleave', '[data-src]', mouseleaveCellHandler); + dfPaneBuilt = true; + } + + updateAllFirewallCells(); +}; + +/******************************************************************************/ + +const hostnameCompare = function(a, b) { + let ha = a; + if ( !reIP.test(ha) ) { + ha = hostnameToSortableTokenMap.get(ha) || ' '; + } + let hb = b; + if ( !reIP.test(hb) ) { + hb = hostnameToSortableTokenMap.get(hb) || ' '; + } + const ca = ha.charCodeAt(0); + const cb = hb.charCodeAt(0); + return ca !== cb ? ca - cb : ha.localeCompare(hb); +}; + +const reIP = /(\d|\])$/; + +/******************************************************************************/ + +const renderPrivacyExposure = function() { + const allDomains = {}; + let allDomainCount = 0; + let touchedDomainCount = 0; + + allHostnameRows.length = 0; + + // Sort hostnames. First-party hostnames must always appear at the top + // of the list. + const { hostnameDict } = popupData; + const desHostnameDone = new Set(); + const keys = Object.keys(hostnameDict).sort(hostnameCompare); + for ( const des of keys ) { + // Specific-type rules -- these are built-in + if ( des === '*' || desHostnameDone.has(des) ) { continue; } + const hnDetails = hostnameDict[des]; + const { domain, counts } = hnDetails; + if ( allDomains.hasOwnProperty(domain) === false ) { + allDomains[domain] = false; + allDomainCount += 1; + } + if ( gtz(counts.allowed.any) ) { + if ( allDomains[domain] === false ) { + allDomains[domain] = true; + touchedDomainCount += 1; + } + } + const dnDetails = hostnameDict[domain]; + if ( dnDetails !== undefined ) { + if ( des !== domain ) { + dnDetails.hasSubdomains = true; + } else if ( dnDetails.hasSubdomains === undefined ) { + dnDetails.hasSubdomains = false; + } + } + allHostnameRows.push(des); + desHostnameDone.add(des); + } + + const summary = domainsHitStr + .replace('{{count}}', touchedDomainCount.toLocaleString()) + .replace('{{total}}', allDomainCount.toLocaleString()); + uDom.nodeFromSelector( + '[data-i18n^="popupDomainsConnected"] + span' + ).textContent = summary; +}; + +/******************************************************************************/ + +const updateHnSwitches = function() { + uDom.nodeFromId('no-popups').classList.toggle( + 'on', popupData.noPopups === true + ); + uDom.nodeFromId('no-large-media').classList.toggle( + 'on', popupData.noLargeMedia === true + ); + uDom.nodeFromId('no-cosmetic-filtering').classList.toggle( + 'on', + popupData.noCosmeticFiltering === true + ); + uDom.nodeFromId('no-remote-fonts').classList.toggle( + 'on', + popupData.noRemoteFonts === true + ); + uDom.nodeFromId('no-scripting').classList.toggle( + 'on', + popupData.noScripting === true + ); +}; + +/******************************************************************************/ + +// Assume everything has to be done incrementally. + +const renderPopup = function() { + if ( popupData.tabTitle ) { + document.title = popupData.appName + ' - ' + popupData.tabTitle; + } + + const isFiltering = popupData.netFilteringSwitch; + + const body = document.body; + body.classList.toggle('advancedUser', popupData.advancedUserEnabled === true); + body.classList.toggle('off', popupData.pageURL === '' || isFiltering !== true); + body.classList.toggle('needSave', popupData.matrixIsDirty === true); + + // The hostname information below the power switch + { + const [ elemHn, elemDn ] = uDom.nodeFromId('hostname').children; + const { pageDomain, pageHostname } = popupData; + if ( pageDomain !== '' ) { + elemDn.textContent = safePunycodeToUnicode(pageDomain); + elemHn.textContent = pageHostname !== pageDomain + ? safePunycodeToUnicode(pageHostname.slice(0, -pageDomain.length - 1)) + '.' + : ''; + } else { + elemHn.textContent = elemDn.textContent = ''; + } + } + + uDom.nodeFromId('basicTools').classList.toggle( + 'canPick', + popupData.canElementPicker === true && isFiltering + ); + + let blocked, total; + if ( popupData.pageCounts !== undefined ) { + const counts = popupData.pageCounts; + blocked = counts.blocked.any; + total = blocked + counts.allowed.any; + } else { + blocked = 0; + total = 0; + } + let text; + if ( total === 0 ) { + text = formatNumber(0); + } else { + text = statsStr.replace('{{count}}', formatNumber(blocked)) + .replace('{{percent}}', formatNumber(Math.floor(blocked * 100 / total))); + } + uDom.nodeFromSelector('[data-i18n^="popupBlockedOnThisPage"] + span').textContent = text; + + blocked = popupData.globalBlockedRequestCount; + total = popupData.globalAllowedRequestCount + blocked; + if ( total === 0 ) { + text = formatNumber(0); + } else { + text = statsStr.replace('{{count}}', formatNumber(blocked)) + .replace('{{percent}}', formatNumber(Math.floor(blocked * 100 / total))); + } + uDom.nodeFromSelector('[data-i18n^="popupBlockedSinceInstall"] + span').textContent = text; + + // This will collate all domains, touched or not + renderPrivacyExposure(); + + // Extra tools + updateHnSwitches(); + + // Report popup count on badge + total = popupData.popupBlockedCount; + uDom.nodeFromSelector('#no-popups .fa-icon-badge') + .textContent = total ? Math.min(total, 99).toLocaleString() : ''; + + // Report large media count on badge + total = popupData.largeMediaCount; + uDom.nodeFromSelector('#no-large-media .fa-icon-badge') + .textContent = total ? Math.min(total, 99).toLocaleString() : ''; + + // Report remote font count on badge + total = popupData.remoteFontCount; + uDom.nodeFromSelector('#no-remote-fonts .fa-icon-badge') + .textContent = total ? Math.min(total, 99).toLocaleString() : ''; + + document.documentElement.classList.toggle( + 'colorBlind', + popupData.colorBlindFriendly === true + ); + + setGlobalExpand(popupData.firewallPaneMinimized === false, true); + + // Build dynamic filtering pane only if in use + if ( (computedSections() & sectionFirewallBit) !== 0 ) { + buildAllFirewallRows(); + } + + renderTooltips(); +}; + +/******************************************************************************/ + +// https://github.com/gorhill/uBlock/issues/2889 +// Use tooltip for ARIA purpose. + +const renderTooltips = function(selector) { + for ( const [ key, details ] of tooltipTargetSelectors ) { + if ( selector !== undefined && key !== selector ) { continue; } + const elem = uDom.nodeFromSelector(key); + if ( elem.hasAttribute('title') === false ) { continue; } + const text = vAPI.i18n( + details.i18n + + (uDom.nodeFromSelector(details.state) === null ? '1' : '2') + ); + elem.setAttribute('aria-label', text); + elem.setAttribute('title', text); + } +}; + +const tooltipTargetSelectors = new Map([ + [ + '#switch', + { + state: 'body.off', + i18n: 'popupPowerSwitchInfo', + } + ], + [ + '#no-popups', + { + state: '#no-popups.on', + i18n: 'popupTipNoPopups' + } + ], + [ + '#no-large-media', + { + state: '#no-large-media.on', + i18n: 'popupTipNoLargeMedia' + } + ], + [ + '#no-cosmetic-filtering', + { + state: '#no-cosmetic-filtering.on', + i18n: 'popupTipNoCosmeticFiltering' + } + ], + [ + '#no-remote-fonts', + { + state: '#no-remote-fonts.on', + i18n: 'popupTipNoRemoteFonts' + } + ], + [ + '#no-scripting', + { + state: '#no-scripting.on', + i18n: 'popupTipNoScripting' + } + ], +]); + +/******************************************************************************/ + +// All rendering code which need to be executed only once. + +let renderOnce = function() { + renderOnce = function(){}; + + const body = document.body; + + if ( popupData.fontSize !== popupFontSize ) { + popupFontSize = popupData.fontSize; + if ( popupFontSize !== 'unset' ) { + body.style.setProperty('--font-size', popupFontSize); + vAPI.localStorage.setItem('popupFontSize', popupFontSize); + } else { + body.style.removeProperty('--font-size'); + vAPI.localStorage.removeItem('popupFontSize'); + } + } + + uDom.nodeFromId('version').textContent = popupData.appVersion; + + sectionBitsToAttribute(computedSections()); + + if ( popupData.uiPopupConfig !== undefined ) { + document.body.setAttribute('data-ui', popupData.uiPopupConfig); + } + + body.classList.toggle('no-tooltips', popupData.tooltipsDisabled === true); + if ( popupData.tooltipsDisabled === true ) { + uDom('[title]').removeAttr('title'); + } + + // https://github.com/uBlockOrigin/uBlock-issues/issues/22 + if ( popupData.advancedUserEnabled !== true ) { + uDom('#firewall [title][data-src]').removeAttr('title'); + } + + // This must be done the firewall is populated + if ( popupData.popupPanelHeightMode === 1 ) { + body.classList.add('vMin'); + } + + // Prevent non-advanced user opting into advanced user mode from harming + // themselves by disabling by default features generally suitable to + // filter list maintainers and actual advanced users. + if ( popupData.godMode ) { + body.classList.add('godMode'); + } +}; + +/******************************************************************************/ + +const renderPopupLazy = (( ) => { + let mustRenderCosmeticFilteringBadge = true; + + // https://github.com/uBlockOrigin/uBlock-issues/issues/756 + // Launch potentially expensive hidden elements-counting scriptlet on + // demand only. + { + const sw = uDom.nodeFromId('no-cosmetic-filtering'); + const badge = sw.querySelector(':scope .fa-icon-badge'); + badge.textContent = '\u22EF'; + + const render = ( ) => { + if ( mustRenderCosmeticFilteringBadge === false ) { return; } + mustRenderCosmeticFilteringBadge = false; + if ( sw.classList.contains('hnSwitchBusy') ) { return; } + sw.classList.add('hnSwitchBusy'); + messaging.send('popupPanel', { + what: 'getHiddenElementCount', + tabId: popupData.tabId, + }).then(count => { + let text; + if ( (count || 0) === 0 ) { + text = ''; + } else if ( count === -1 ) { + text = '?'; + } else { + text = Math.min(count, 99).toLocaleString(); + } + badge.textContent = text; + sw.classList.remove('hnSwitchBusy'); + }); + }; + + sw.addEventListener('mouseenter', render, { passive: true }); + } + + return async function() { + const count = await messaging.send('popupPanel', { + what: 'getScriptCount', + tabId: popupData.tabId, + }); + uDom.nodeFromSelector('#no-scripting .fa-icon-badge') + .textContent = (count || 0) !== 0 + ? Math.min(count, 99).toLocaleString() + : ''; + mustRenderCosmeticFilteringBadge = true; + }; +})(); + +/******************************************************************************/ + +const toggleNetFilteringSwitch = function(ev) { + if ( !popupData || !popupData.pageURL ) { return; } + messaging.send('popupPanel', { + what: 'toggleNetFiltering', + url: popupData.pageURL, + scope: ev.ctrlKey || ev.metaKey ? 'page' : '', + state: !uDom('body').toggleClass('off').hasClass('off'), + tabId: popupData.tabId, + }); + renderTooltips('#switch'); + hashFromPopupData(); +}; + +/******************************************************************************/ + +const gotoZap = function() { + messaging.send('popupPanel', { + what: 'launchElementPicker', + tabId: popupData.tabId, + zap: true, + }); + + vAPI.closePopup(); +}; + +/******************************************************************************/ + +const gotoPick = function() { + messaging.send('popupPanel', { + what: 'launchElementPicker', + tabId: popupData.tabId, + }); + + vAPI.closePopup(); +}; + +/******************************************************************************/ + +const gotoReport = function() { + const popupPanel = { + blocked: popupData.pageCounts.blocked.any, + }; + const reportedStates = [ + { name: 'enabled', prop: 'netFilteringSwitch', expected: true }, + { name: 'no-cosmetic-filtering', prop: 'noCosmeticFiltering', expected: false }, + { name: 'no-large-media', prop: 'noLargeMedia', expected: false }, + { name: 'no-popups', prop: 'noPopups', expected: false }, + { name: 'no-remote-fonts', prop: 'noRemoteFonts', expected: false }, + { name: 'no-scripting', prop: 'noScripting', expected: false }, + { name: 'can-element-picker', prop: 'canElementPicker', expected: true }, + ]; + for ( const { name, prop, expected } of reportedStates ) { + if ( popupData[prop] === expected ) { continue; } + popupPanel[name] = !expected; + } + if ( hostnameToSortableTokenMap.size !== 0 ) { + const blockedDetails = {}; + const hostnames = + Array.from(hostnameToSortableTokenMap.keys()).sort(hostnameCompare); + for ( const hostname of hostnames ) { + const entry = popupData.hostnameDict[hostname]; + const count = entry.counts.blocked.any; + if ( count === 0 ) { continue; } + const domain = entry.domain; + if ( blockedDetails[domain] === undefined ) { + blockedDetails[domain] = 0; + } + blockedDetails[domain] += count; + } + if ( Object.keys(blockedDetails).length !== 0 ) { + popupPanel.blockedDetails = blockedDetails; + } + } + messaging.send('popupPanel', { + what: 'launchReporter', + tabId: popupData.tabId, + pageURL: popupData.pageURL, + popupPanel: JSON.stringify(popupPanel), + }); + + vAPI.closePopup(); +}; + +/******************************************************************************/ + +const gotoURL = function(ev) { + if ( this.hasAttribute('href') === false ) { return; } + + ev.preventDefault(); + + let url = this.getAttribute('href'); + if ( + url === 'logger-ui.html#_' && + typeof popupData.tabId === 'number' + ) { + url += '+' + popupData.tabId; + } + + messaging.send('popupPanel', { + what: 'gotoURL', + details: { + url: url, + select: true, + index: -1, + shiftKey: ev.shiftKey + }, + }); + + vAPI.closePopup(); +}; + +/******************************************************************************/ + +// The popup panel is made of sections. Visibility of sections can +// be toggled on/off. + +const maxNumberOfSections = 6; +const sectionFirewallBit = 0b10000; + +const computedSections = ( ) => + popupData.popupPanelSections & + ~popupData.popupPanelDisabledSections | + popupData.popupPanelLockedSections; + +const sectionBitsFromAttribute = function() { + const attr = document.body.dataset.more; + if ( attr === '' ) { return 0; } + let bits = 0; + for ( const c of attr.split(' ') ) { + bits |= 1 << (c.charCodeAt(0) - 97); + } + return bits; +}; + +const sectionBitsToAttribute = function(bits) { + const attr = []; + for ( let i = 0; i < maxNumberOfSections; i++ ) { + const bit = 1 << i; + if ( (bits & bit) === 0 ) { continue; } + attr.push(String.fromCharCode(97 + i)); + } + document.body.dataset.more = attr.join(' '); +}; + +const toggleSections = function(more) { + const offbits = ~popupData.popupPanelDisabledSections; + const onbits = popupData.popupPanelLockedSections; + let currentBits = sectionBitsFromAttribute(); + let newBits = currentBits; + for ( let i = 0; i < maxNumberOfSections; i++ ) { + const bit = 1 << (more ? i : maxNumberOfSections - i - 1); + if ( more ) { + newBits |= bit; + } else { + newBits &= ~bit; + } + newBits = newBits & offbits | onbits; + if ( newBits !== currentBits ) { break; } + } + if ( newBits === currentBits ) { return; } + + sectionBitsToAttribute(newBits); + + popupData.popupPanelSections = newBits; + messaging.send('popupPanel', { + what: 'userSettings', + name: 'popupPanelSections', + value: newBits, + }); + + // https://github.com/chrisaljoudi/uBlock/issues/996 + // Remember the last state of the firewall pane. This allows to + // configure the popup size early next time it is opened, which means a + // less glitchy popup at open time. + vAPI.localStorage.setItem('popupPanelSections', newBits); + + // Dynamic filtering pane may not have been built yet + if ( (newBits & sectionFirewallBit) !== 0 && dfPaneBuilt === false ) { + buildAllFirewallRows(); + } +}; + +uDom('#moreButton').on('click', ( ) => { toggleSections(true); }); +uDom('#lessButton').on('click', ( ) => { toggleSections(false); }); + +/******************************************************************************/ + +const mouseenterCellHandler = function(ev) { + const target = ev.target; + if ( target.classList.contains('ownRule') ) { return; } + target.appendChild(dfHotspots); +}; + +const mouseleaveCellHandler = function() { + dfHotspots.remove(); +}; + +/******************************************************************************/ + +const setFirewallRule = async function(src, des, type, action, persist) { + // This can happen on pages where uBlock does not work + if ( + typeof popupData.pageHostname !== 'string' || + popupData.pageHostname === '' + ) { + return; + } + + const response = await messaging.send('popupPanel', { + what: 'toggleFirewallRule', + tabId: popupData.tabId, + pageHostname: popupData.pageHostname, + srcHostname: src, + desHostname: des, + requestType: type, + action: action, + persist: persist, + }); + + // Remove action widget if an own rule has been set, this allows to click + // again immediately to remove the rule. + if ( action !== 0 ) { + dfHotspots.remove(); + } + + cachePopupData(response); + updateAllFirewallCells(true, false); + hashFromPopupData(); +}; + +/******************************************************************************/ + +const unsetFirewallRuleHandler = function(ev) { + const cell = ev.target; + const row = cell.closest('[data-des]'); + setFirewallRule( + cell.getAttribute('data-src') === '/' ? '*' : popupData.pageHostname, + row.getAttribute('data-des'), + row.getAttribute('data-type'), + 0, + ev.ctrlKey || ev.metaKey + ); + cell.appendChild(dfHotspots); +}; + +/******************************************************************************/ + +const setFirewallRuleHandler = function(ev) { + const hotspot = ev.target; + const cell = hotspot.closest('[data-src]'); + if ( cell === null ) { return; } + const row = cell.closest('[data-des]'); + let action = 0; + if ( hotspot.id === 'dynaAllow' ) { + action = 2; + } else if ( hotspot.id === 'dynaNoop' ) { + action = 3; + } else { + action = 1; + } + setFirewallRule( + cell.getAttribute('data-src') === '/' ? '*' : popupData.pageHostname, + row.getAttribute('data-des'), + row.getAttribute('data-type'), + action, + ev.ctrlKey || ev.metaKey + ); + dfHotspots.remove(); +}; + +/******************************************************************************/ + +const reloadTab = function(ev) { + messaging.send('popupPanel', { + what: 'reloadTab', + tabId: popupData.tabId, + select: vAPI.webextFlavor.soup.has('mobile'), + bypassCache: ev.ctrlKey || ev.metaKey || ev.shiftKey, + }); + + // Polling will take care of refreshing the popup content + // https://github.com/chrisaljoudi/uBlock/issues/748 + // User forces a reload, assume the popup has to be updated regardless + // if there were changes or not. + popupData.contentLastModified = -1; + + // Reset popup state hash to current state. + hashFromPopupData(true); +}; + +uDom('#refresh').on('click', reloadTab); + +// https://github.com/uBlockOrigin/uBlock-issues/issues/672 +document.addEventListener( + 'keydown', + ev => { + if ( ev.code !== 'F5' ) { return; } + reloadTab(ev); + ev.preventDefault(); + ev.stopPropagation(); + }, + { capture: true } +); + +/******************************************************************************/ + +const expandExceptions = new Set(); + +vAPI.localStorage.getItemAsync('popupExpandExceptions').then(exceptions => { + try { + if ( Array.isArray(exceptions) === false ) { return; } + for ( const exception of exceptions ) { + expandExceptions.add(exception); + } + } + catch(ex) { + } +}); + +const saveExpandExceptions = function() { + vAPI.localStorage.setItem( + 'popupExpandExceptions', + Array.from(expandExceptions) + ); +}; + +const setGlobalExpand = function(state, internal = false) { + uDom('.expandException').removeClass('expandException'); + if ( state ) { + uDom('#firewall').addClass('expanded'); + } else { + uDom('#firewall').removeClass('expanded'); + } + if ( internal ) { return; } + popupData.firewallPaneMinimized = !state; + expandExceptions.clear(); + saveExpandExceptions(); + messaging.send('popupPanel', { + what: 'userSettings', + name: 'firewallPaneMinimized', + value: popupData.firewallPaneMinimized, + }); +}; + +const setSpecificExpand = function(domain, state, internal = false) { + const unodes = uDom(`[data-des="${domain}"],[data-des$=".${domain}"]`); + if ( state ) { + unodes.addClass('expandException'); + } else { + unodes.removeClass('expandException'); + } + if ( internal ) { return; } + if ( state ) { + expandExceptions.add(domain); + } else { + expandExceptions.delete(domain); + } + saveExpandExceptions(); +}; + +uDom('[data-i18n="popupAnyRulePrompt"]').on('click', ev => { + // Special display mode: in its own tab/window, with no vertical restraint. + // Useful to take snapshots of the whole list of domains -- example: + // https://github.com/gorhill/uBlock/issues/736#issuecomment-178879944 + if ( ev.shiftKey && ev.ctrlKey ) { + messaging.send('popupPanel', { + what: 'gotoURL', + details: { + url: `popup-fenix.html?tabId=${popupData.tabId}&intab=1`, + select: true, + index: -1, + }, + }); + vAPI.closePopup(); + return; + } + + setGlobalExpand( + uDom('#firewall').hasClass('expanded') === false + ); +}); + +uDom('#firewall').on( + 'click', '.isDomain[data-type="*"] > span:first-of-type', + ev => { + const div = ev.target.closest('[data-des]'); + if ( div === null ) { return; } + setSpecificExpand( + div.getAttribute('data-des'), + div.classList.contains('expandException') === false + ); + } +); + +/******************************************************************************/ + +const saveFirewallRules = function() { + messaging.send('popupPanel', { + what: 'saveFirewallRules', + srcHostname: popupData.pageHostname, + desHostnames: popupData.hostnameDict, + }); + document.body.classList.remove('needSave'); +}; + +/******************************************************************************/ + +const revertFirewallRules = async function() { + document.body.classList.remove('needSave'); + const response = await messaging.send('popupPanel', { + what: 'revertFirewallRules', + srcHostname: popupData.pageHostname, + desHostnames: popupData.hostnameDict, + tabId: popupData.tabId, + }); + cachePopupData(response); + updateAllFirewallCells(true, false); + updateHnSwitches(); + hashFromPopupData(); +}; + +/******************************************************************************/ + +const toggleHostnameSwitch = async function(ev) { + const target = ev.currentTarget; + const switchName = target.getAttribute('id'); + if ( !switchName ) { return; } + // For touch displays, process click only if the switch is not "busy". + if ( + vAPI.webextFlavor.soup.has('mobile') && + target.classList.contains('hnSwitchBusy') + ) { + return; + } + target.classList.toggle('on'); + renderTooltips(`#${switchName}`); + + const response = await messaging.send('popupPanel', { + what: 'toggleHostnameSwitch', + name: switchName, + hostname: popupData.pageHostname, + state: target.classList.contains('on'), + tabId: popupData.tabId, + persist: ev.ctrlKey || ev.metaKey, + }); + + cachePopupData(response); + hashFromPopupData(); + + document.body.classList.toggle('needSave', popupData.matrixIsDirty === true); +}; + +/******************************************************************************* + + Double tap ctrl key: toggle god mode + +*/ + +{ + let eventCount = 0; + let eventTime = 0; + + document.addEventListener('keydown', ev => { + if ( ev.key !== 'Control' ) { + eventCount = 0; + return; + } + const now = Date.now(); + if ( (now - eventTime) >= 500 ) { + eventCount = 0; + } + eventCount += 1; + eventTime = now; + if ( eventCount < 2 ) { return; } + eventCount = 0; + document.body.classList.toggle('godMode'); + }); +} + + +/******************************************************************************/ + +// Poll for changes. +// +// I couldn't find a better way to be notified of changes which can affect +// popup content, as the messaging API doesn't support firing events accurately +// from the main extension process to a specific auxiliary extension process: +// +// - broadcasting() is not an option given there could be a lot of tabs opened, +// and maybe even many frames within these tabs, i.e. unacceptable overhead +// regardless of whether the popup is opened or not. +// +// - Modifying the messaging API is not an option, as this would require +// revisiting all platform-specific code to support targeted broadcasting, +// which who knows could be not so trivial for some platforms. +// +// A well done polling is a better anyways IMO, I prefer that data is pulled +// on demand rather than forcing the main process to assume a client may need +// it and thus having to push it all the time unconditionally. + +const pollForContentChange = (( ) => { + let pollTimer; + + const pollCallback = async function() { + pollTimer = undefined; + const response = await messaging.send('popupPanel', { + what: 'hasPopupContentChanged', + tabId: popupData.tabId, + contentLastModified: popupData.contentLastModified, + }); + queryCallback(response); + }; + + const queryCallback = function(response) { + if ( response ) { + getPopupData(popupData.tabId); + return; + } + poll(); + }; + + const poll = function() { + if ( pollTimer !== undefined ) { return; } + pollTimer = vAPI.setTimeout(pollCallback, 1500); + }; + + return poll; +})(); + +/******************************************************************************/ + +const getPopupData = async function(tabId, first = false) { + const response = await messaging.send('popupPanel', { + what: 'getPopupData', + tabId, + }); + + cachePopupData(response); + renderOnce(); + renderPopup(); + renderPopupLazy(); // low priority rendering + hashFromPopupData(first); + pollForContentChange(); +}; + +/******************************************************************************/ + +// Popup DOM is assumed to be loaded at this point -- because this script +// is loaded after everything else. + +{ + // Extract the tab id of the page for this popup. If there's no tab id + // specified in the query string, it will default to current tab. + const selfURL = new URL(self.location.href); + const tabId = parseInt(selfURL.searchParams.get('tabId'), 10) || null; + + const nextFrames = async n => { + for ( let i = 0; i < n; i++ ) { + await new Promise(resolve => { + self.requestAnimationFrame(( ) => { resolve(); }); + }); + } + }; + + // The purpose of the following code is to reset to a vertical layout + // should the viewport not be enough wide to accommodate the horizontal + // layout. + // To avoid querying a spurious viewport width -- it happens sometimes, + // somehow -- we delay layout-changing operations to the next paint + // frames. + // Force a layout recalculation by querying the body width. To be + // honest, I have no clue if this makes a difference in the end. + // https://gist.github.com/paulirish/5d52fb081b3570c81e3a + // Use a tolerance proportional to the sum of the width of the panes + // when testing against viewport width. + const checkViewport = async function() { + const root = document.querySelector(':root'); + if ( + root.classList.contains('mobile') || + selfURL.searchParams.get('portrait') + ) { + root.classList.add('portrait'); + } else if ( root.classList.contains('desktop') ) { + await nextFrames(4); + const main = document.getElementById('main'); + const firewall = document.getElementById('firewall'); + const minWidth = (main.offsetWidth + firewall.offsetWidth) / 1.1; + if ( + selfURL.searchParams.get('portrait') || + window.innerWidth < minWidth + ) { + root.classList.add('portrait'); + } + } + if ( root.classList.contains('portrait') ) { + const panes = document.getElementById('panes'); + const sticky = document.getElementById('sticky'); + const stickyParent = sticky.parentElement; + if ( stickyParent !== panes ) { + panes.prepend(sticky); + } + } + if ( selfURL.searchParams.get('intab') !== null ) { + root.classList.add('intab'); + } + await nextFrames(1); + document.body.classList.remove('loading'); + }; + + getPopupData(tabId, true).then(( ) => { + if ( document.readyState !== 'complete' ) { + self.addEventListener('load', ( ) => { checkViewport(); }, { once: true }); + } else { + checkViewport(); + } + }); +} + +/******************************************************************************/ + +uDom('#switch').on('click', toggleNetFilteringSwitch); +uDom('#gotoZap').on('click', gotoZap); +uDom('#gotoPick').on('click', gotoPick); +uDom('#gotoReport').on('click', gotoReport); +uDom('.hnSwitch').on('click', ev => { toggleHostnameSwitch(ev); }); +uDom('#saveRules').on('click', saveFirewallRules); +uDom('#revertRules').on('click', ( ) => { revertFirewallRules(); }); +uDom('a[href]').on('click', gotoURL); + +/******************************************************************************/ + +// Toggle emphasis of rows with[out] 3rd-party scripts/frames +document.querySelector('#firewall > [data-type="3p-script"] .filter') + .addEventListener('click', ( ) => { + document.getElementById('firewall').classList.toggle('show3pScript'); + }); + +// Toggle visibility of rows with[out] 3rd-party frames +document.querySelector('#firewall > [data-type="3p-frame"] .filter') + .addEventListener('click', ( ) => { + document.getElementById('firewall').classList.toggle('show3pFrame'); + }); + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/redirect-engine.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/redirect-engine.js new file mode 100644 index 0000000..e30a802 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/redirect-engine.js @@ -0,0 +1,617 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-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 +*/ + +'use strict'; + +/******************************************************************************/ + +import { + LineIterator, + orphanizeString, +} from './text-utils.js'; + +/******************************************************************************/ + +// The resources referenced below are found in ./web_accessible_resources/ +// +// The content of the resources which declare a `data` property will be loaded +// in memory, and converted to a suitable internal format depending on the +// type of the loaded data. The `data` property allows for manual injection +// through `+js(...)`, or for redirection to a data: URI when a redirection +// to a web accessible resource is not desirable. + +const redirectableResources = new Map([ + [ '1x1.gif', { + alias: '1x1-transparent.gif', + data: 'blob', + } ], + [ '2x2.png', { + alias: '2x2-transparent.png', + data: 'blob', + } ], + [ '3x2.png', { + alias: '3x2-transparent.png', + data: 'blob', + } ], + [ '32x32.png', { + alias: '32x32-transparent.png', + data: 'blob', + } ], + [ 'addthis_widget.js', { + alias: 'addthis.com/addthis_widget.js', + } ], + [ 'amazon_ads.js', { + alias: 'amazon-adsystem.com/aax2/amzn_ads.js', + data: 'text', + } ], + [ 'amazon_apstag.js', { + } ], + [ 'ampproject_v0.js', { + alias: 'ampproject.org/v0.js', + } ], + [ 'chartbeat.js', { + alias: 'static.chartbeat.com/chartbeat.js', + } ], + [ 'click2load.html', { + params: [ 'aliasURL', 'url' ], + } ], + [ 'doubleclick_instream_ad_status.js', { + alias: 'doubleclick.net/instream/ad_status.js', + data: 'text', + } ], + [ 'empty', { + data: 'text', // Important! + } ], + [ 'fingerprint2.js', { + data: 'text', + } ], + [ 'fingerprint3.js', { + data: 'text', + } ], + [ 'google-analytics_analytics.js', { + alias: [ + 'google-analytics.com/analytics.js', + 'googletagmanager_gtm.js', + 'googletagmanager.com/gtm.js' + ], + data: 'text', + } ], + [ 'google-analytics_cx_api.js', { + alias: 'google-analytics.com/cx/api.js', + } ], + [ 'google-analytics_ga.js', { + alias: 'google-analytics.com/ga.js', + data: 'text', + } ], + [ 'google-analytics_inpage_linkid.js', { + alias: 'google-analytics.com/inpage_linkid.js', + } ], + [ 'googlesyndication_adsbygoogle.js', { + alias: 'googlesyndication.com/adsbygoogle.js', + data: 'text', + } ], + [ 'googletagservices_gpt.js', { + alias: 'googletagservices.com/gpt.js', + data: 'text', + } ], + [ 'hd-main.js', { + } ], + [ 'ligatus_angular-tag.js', { + alias: 'ligatus.com/*/angular-tag.js', + } ], + [ 'mxpnl_mixpanel.js', { + } ], + [ 'monkeybroker.js', { + alias: 'd3pkae9owd2lcf.cloudfront.net/mb105.js', + } ], + [ 'noeval.js', { + data: 'text', + } ], + [ 'noeval-silent.js', { + alias: 'silent-noeval.js', + data: 'text', + } ], + [ 'nobab.js', { + alias: 'bab-defuser.js', + data: 'text', + } ], + [ 'nobab2.js', { + data: 'text', + } ], + [ 'nofab.js', { + alias: 'fuckadblock.js-3.2.0', + data: 'text', + } ], + [ 'noop-0.1s.mp3', { + alias: [ 'noopmp3-0.1s', 'abp-resource:blank-mp3' ], + data: 'blob', + } ], + [ 'noop-1s.mp4', { + alias: 'noopmp4-1s', + data: 'blob', + } ], + [ 'noop.html', { + alias: 'noopframe', + } ], + [ 'noop.js', { + alias: [ 'noopjs', 'abp-resource:blank-js' ], + data: 'text', + } ], + [ 'noop.txt', { + alias: 'nooptext', + data: 'text', + } ], + [ 'noop-vmap1.0.xml', { + alias: 'noopvmap-1.0', + data: 'text', + } ], + [ 'outbrain-widget.js', { + alias: 'widgets.outbrain.com/outbrain.js', + } ], + [ 'popads.js', { + alias: 'popads.net.js', + data: 'text', + } ], + [ 'popads-dummy.js', { + data: 'text', + } ], + [ 'prebid-ads.js', { + data: 'text', + } ], + [ 'scorecardresearch_beacon.js', { + alias: 'scorecardresearch.com/beacon.js', + } ], + [ 'window.open-defuser.js', { + alias: 'nowoif.js', + data: 'text', + } ], +]); + +const extToMimeMap = new Map([ + [ 'gif', 'image/gif' ], + [ 'html', 'text/html' ], + [ 'js', 'application/javascript' ], + [ 'mp3', 'audio/mp3' ], + [ 'mp4', 'video/mp4' ], + [ 'png', 'image/png' ], + [ 'txt', 'text/plain' ], + [ 'xml', 'text/xml' ], +]); + +const typeToMimeMap = new Map([ + [ 'main_frame', 'text/html' ], + [ 'other', 'text/plain' ], + [ 'script', 'application/javascript' ], + [ 'stylesheet', 'text/css' ], + [ 'sub_frame', 'text/html' ], + [ 'xmlhttprequest', 'text/plain' ], +]); + +const validMimes = new Set(extToMimeMap.values()); + +const mimeFromName = function(name) { + const match = /\.([^.]+)$/.exec(name); + if ( match !== null ) { + return extToMimeMap.get(match[1]); + } +}; + +// vAPI.warSecret() is optional, it could be absent in some environments, +// i.e. nodejs for example. Probably the best approach is to have the +// "web_accessible_resources secret" added outside by the client of this +// module, but for now I just want to remove an obstacle to modularization. +const warSecret = typeof vAPI === 'object' && vAPI !== null + ? vAPI.warSecret + : ( ) => ''; + +/******************************************************************************/ +/******************************************************************************/ + +const RedirectEntry = class { + constructor() { + this.mime = ''; + this.data = ''; + this.warURL = undefined; + this.params = undefined; + } + + // Prevent redirection to web accessible resources when the request is + // of type 'xmlhttprequest', because XMLHttpRequest.responseURL would + // cause leakage of extension id. See: + // - https://stackoverflow.com/a/8056313 + // - https://bugzilla.mozilla.org/show_bug.cgi?id=998076 + // https://www.reddit.com/r/uBlockOrigin/comments/cpxm1v/ + // User-supplied resources may already be base64 encoded. + + toURL(fctxt, asDataURI = false) { + if ( + this.warURL !== undefined && + asDataURI !== true && + fctxt instanceof Object && + fctxt.type !== 'xmlhttprequest' + ) { + const params = []; + const secret = warSecret(); + if ( secret !== '' ) { params.push(`secret=${secret}`); } + if ( this.params !== undefined ) { + for ( const name of this.params ) { + const value = fctxt[name]; + if ( value === undefined ) { continue; } + params.push(`${name}=${encodeURIComponent(value)}`); + } + } + let url = `${this.warURL}`; + if ( params.length !== 0 ) { + url += `?${params.join('&')}`; + } + return url; + } + if ( this.data === undefined ) { return; } + // https://github.com/uBlockOrigin/uBlock-issues/issues/701 + if ( this.data === '' ) { + const mime = typeToMimeMap.get(fctxt.type); + if ( mime === undefined ) { return; } + return `data:${mime},`; + } + if ( this.data.startsWith('data:') === false ) { + if ( this.mime.indexOf(';') === -1 ) { + this.data = `data:${this.mime};base64,${btoa(this.data)}`; + } else { + this.data = `data:${this.mime},${this.data}`; + } + } + return this.data; + } + + toContent() { + if ( this.data.startsWith('data:') ) { + const pos = this.data.indexOf(','); + const base64 = this.data.endsWith(';base64', pos); + this.data = this.data.slice(pos + 1); + if ( base64 ) { + this.data = atob(this.data); + } + } + return this.data; + } + + static fromContent(mime, content) { + const r = new RedirectEntry(); + r.mime = mime; + r.data = content; + return r; + } + + static fromSelfie(selfie) { + const r = new RedirectEntry(); + r.mime = selfie.mime; + r.data = selfie.data; + r.warURL = selfie.warURL; + r.params = selfie.params; + return r; + } +}; + +/******************************************************************************/ +/******************************************************************************/ + +const RedirectEngine = function() { + this.aliases = new Map(); + this.resources = new Map(); + this.reset(); + this.modifyTime = Date.now(); + this.resourceNameRegister = ''; +}; + +/******************************************************************************/ + +RedirectEngine.prototype.reset = function() { +}; + +/******************************************************************************/ + +RedirectEngine.prototype.freeze = function() { +}; + +/******************************************************************************/ + +RedirectEngine.prototype.tokenToURL = function( + fctxt, + token, + asDataURI = false +) { + const entry = this.resources.get(this.aliases.get(token) || token); + if ( entry === undefined ) { return; } + this.resourceNameRegister = token; + return entry.toURL(fctxt, asDataURI); +}; + +/******************************************************************************/ + +RedirectEngine.prototype.hasToken = function(token) { + if ( token === 'none' ) { return true; } + const asDataURI = token.charCodeAt(0) === 0x25 /* '%' */; + if ( asDataURI ) { + token = token.slice(1); + } + return this.resources.get(this.aliases.get(token) || token) !== undefined; +}; + +/******************************************************************************/ + +RedirectEngine.prototype.toSelfie = async function() { +}; + +/******************************************************************************/ + +RedirectEngine.prototype.fromSelfie = async function() { + return true; +}; + +/******************************************************************************/ + +RedirectEngine.prototype.resourceContentFromName = function(name, mime) { + const entry = this.resources.get(this.aliases.get(name) || name); + if ( entry === undefined ) { return; } + if ( mime === undefined || entry.mime.startsWith(mime) ) { + return entry.toContent(); + } +}; + +/******************************************************************************/ + +// https://github.com/uBlockOrigin/uAssets/commit/deefe875551197d655f79cb540e62dfc17c95f42 +// Consider 'none' a reserved keyword, to be used to disable redirection. +// https://github.com/uBlockOrigin/uBlock-issues/issues/1419 +// Append newlines to raw text to ensure processing of trailing resource. + +RedirectEngine.prototype.resourcesFromString = function(text) { + const lineIter = new LineIterator( + removeTopCommentBlock(text) + '\n\n' + ); + const reNonEmptyLine = /\S/; + let fields, encoded, details; + + while ( lineIter.eot() === false ) { + const line = lineIter.next(); + if ( line.startsWith('#') ) { continue; } + if ( line.startsWith('// ') ) { continue; } + + if ( fields === undefined ) { + if ( line === '' ) { continue; } + // Modern parser + if ( line.startsWith('/// ') ) { + const name = line.slice(4).trim(); + fields = [ name, mimeFromName(name) ]; + continue; + } + // Legacy parser + const head = line.trim().split(/\s+/); + if ( head.length !== 2 ) { continue; } + if ( head[0] === 'none' ) { continue; } + let pos = head[1].indexOf(';'); + if ( pos === -1 ) { pos = head[1].length; } + if ( validMimes.has(head[1].slice(0, pos)) === false ) { + continue; + } + encoded = head[1].indexOf(';') !== -1; + fields = head; + continue; + } + + if ( line.startsWith('/// ') ) { + if ( details === undefined ) { + details = []; + } + const [ prop, value ] = line.slice(4).trim().split(/\s+/); + if ( value !== undefined ) { + details.push({ prop, value }); + } + continue; + } + + if ( reNonEmptyLine.test(line) ) { + fields.push(encoded ? line.trim() : line); + continue; + } + + // No more data, add the resource. + const name = this.aliases.get(fields[0]) || fields[0]; + const mime = fields[1]; + const content = orphanizeString( + fields.slice(2).join(encoded ? '' : '\n') + ); + this.resources.set( + name, + RedirectEntry.fromContent(mime, content) + ); + if ( Array.isArray(details) ) { + for ( const { prop, value } of details ) { + if ( prop !== 'alias' ) { continue; } + this.aliases.set(value, name); + } + } + + fields = undefined; + details = undefined; + } + + this.modifyTime = Date.now(); +}; + +const removeTopCommentBlock = function(text) { + return text.replace(/^\/\*[\S\s]+?\n\*\/\s*/, ''); +}; + +/******************************************************************************/ + +RedirectEngine.prototype.loadBuiltinResources = function(fetcher) { + this.resources = new Map(); + this.aliases = new Map(); + + const fetches = [ + fetcher( + '/assets/resources/scriptlets.js' + ).then(result => { + const content = result.content; + if ( typeof content !== 'string' ) { return; } + if ( content.length === 0 ) { return; } + this.resourcesFromString(content); + }), + ]; + + const store = (name, data = undefined) => { + const details = redirectableResources.get(name); + const entry = RedirectEntry.fromSelfie({ + mime: mimeFromName(name), + data, + warURL: `/web_accessible_resources/${name}`, + params: details.params, + }); + this.resources.set(name, entry); + if ( details.alias === undefined ) { return; } + if ( Array.isArray(details.alias) ) { + for ( const alias of details.alias ) { + this.aliases.set(alias, name); + } + } else { + this.aliases.set(details.alias, name); + } + }; + + const processBlob = (name, blob) => { + return new Promise(resolve => { + const reader = new FileReader(); + reader.onload = ( ) => { + store(name, reader.result); + resolve(); + }; + reader.onabort = reader.onerror = ( ) => { + resolve(); + }; + reader.readAsDataURL(blob); + }); + }; + + const processText = (name, text) => { + store(name, removeTopCommentBlock(text)); + }; + + const process = result => { + const match = /^\/web_accessible_resources\/([^?]+)/.exec(result.url); + if ( match === null ) { return; } + const name = match[1]; + return result.content instanceof Blob + ? processBlob(name, result.content) + : processText(name, result.content); + }; + + for ( const [ name, details ] of redirectableResources ) { + if ( typeof details.data !== 'string' ) { + store(name); + continue; + } + fetches.push( + fetcher(`/web_accessible_resources/${name}`, { + responseType: details.data + }).then( + result => process(result) + ) + ); + } + + return Promise.all(fetches); +}; + +/******************************************************************************/ + +RedirectEngine.prototype.getResourceDetails = function() { + const out = new Map([ + [ 'none', { canInject: false, canRedirect: true, aliasOf: '' } ], + ]); + for ( const [ name, entry ] of this.resources ) { + out.set(name, { + canInject: typeof entry.data === 'string', + canRedirect: entry.warURL !== undefined, + aliasOf: '', + }); + } + for ( const [ alias, name ] of this.aliases ) { + const original = out.get(name); + if ( original === undefined ) { continue; } + const aliased = Object.assign({}, original); + aliased.aliasOf = name; + out.set(alias, aliased); + } + return Array.from(out).sort((a, b) => { + return a[0].localeCompare(b[0]); + }); +}; + +/******************************************************************************/ + +const RESOURCES_SELFIE_VERSION = 6; +const RESOURCES_SELFIE_NAME = 'compiled/redirectEngine/resources'; + +RedirectEngine.prototype.selfieFromResources = function(storage) { + storage.put( + RESOURCES_SELFIE_NAME, + JSON.stringify({ + version: RESOURCES_SELFIE_VERSION, + aliases: Array.from(this.aliases), + resources: Array.from(this.resources), + }) + ); +}; + +RedirectEngine.prototype.resourcesFromSelfie = async function(storage) { + const result = await storage.get(RESOURCES_SELFIE_NAME); + let selfie; + try { + selfie = JSON.parse(result.content); + } catch(ex) { + } + if ( + selfie instanceof Object === false || + selfie.version !== RESOURCES_SELFIE_VERSION || + Array.isArray(selfie.resources) === false + ) { + return false; + } + this.aliases = new Map(selfie.aliases); + this.resources = new Map(); + for ( const [ token, entry ] of selfie.resources ) { + this.resources.set(token, RedirectEntry.fromSelfie(entry)); + } + return true; +}; + +RedirectEngine.prototype.invalidateResourcesSelfie = function(storage) { + storage.remove(RESOURCES_SELFIE_NAME); +}; + +/******************************************************************************/ + +const redirectEngine = new RedirectEngine(); + +export { redirectEngine }; + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/reverselookup-worker.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/reverselookup-worker.js new file mode 100644 index 0000000..0a5386a --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/reverselookup-worker.js @@ -0,0 +1,301 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-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 +*/ + +'use strict'; + +/******************************************************************************/ + +let listEntries = Object.create(null); + +/******************************************************************************/ + +// https://github.com/uBlockOrigin/uBlock-issues/issues/2092 +// Order of ids matters + +const extractBlocks = function(content, ...ids) { + const out = []; + for ( const id of ids ) { + const pattern = `#block-start-${id}\n`; + let beg = content.indexOf(pattern); + if ( beg === -1 ) { continue; } + beg += pattern.length; + const end = content.indexOf(`#block-end-${id}`, beg); + out.push(content.slice(beg, end)); + } + return out.join('\n'); +}; + +/******************************************************************************/ + +// https://github.com/MajkiIT/polish-ads-filter/issues/14768#issuecomment-536006312 +// Avoid reporting badfilter-ed filters. + +const fromNetFilter = function(details) { + const lists = []; + const compiledFilter = details.compiledFilter; + + for ( const assetKey in listEntries ) { + const entry = listEntries[assetKey]; + if ( entry === undefined ) { continue; } + if ( entry.networkContent === undefined ) { + entry.networkContent = extractBlocks(entry.content, 'NETWORK_FILTERS:GOOD'); + } + const content = entry.networkContent; + let pos = 0; + for (;;) { + pos = content.indexOf(compiledFilter, pos); + if ( pos === -1 ) { break; } + // We need an exact match. + // https://github.com/gorhill/uBlock/issues/1392 + // https://github.com/gorhill/uBlock/issues/835 + const notFound = pos !== 0 && content.charCodeAt(pos - 1) !== 0x0A; + pos += compiledFilter.length; + if ( + notFound || + pos !== content.length && content.charCodeAt(pos) !== 0x0A + ) { + continue; + } + lists.push({ + assetKey: assetKey, + title: entry.title, + supportURL: entry.supportURL + }); + break; + } + } + + const response = {}; + response[details.rawFilter] = lists; + + self.postMessage({ id: details.id, response }); +}; + +/******************************************************************************/ + +// Looking up filter lists from a cosmetic filter is a bit more complicated +// than with network filters: +// +// The filter is its raw representation, not its compiled version. This is +// because the cosmetic filtering engine can't translate a live cosmetic +// filter into its compiled version. Reason is I do not want to burden +// cosmetic filtering with the resource overhead of being able to recompile +// live cosmetic filters. I want the cosmetic filtering code to be left +// completely unaffected by reverse lookup requirements. +// +// Mainly, given a CSS selector and a hostname as context, we will derive +// various versions of compiled filters and see if there are matches. This +// way the whole CPU cost is incurred by the reverse lookup code -- in a +// worker thread, and the cosmetic filtering engine incurs no cost at all. +// +// For this though, the reverse lookup code here needs some knowledge of +// the inners of the cosmetic filtering engine. +// FilterContainer.fromCompiledContent() is our reference code to create +// the various compiled versions. + +const fromExtendedFilter = function(details) { + const match = /^#@?#\^?/.exec(details.rawFilter); + const prefix = match[0]; + const exception = prefix.charAt(1) === '@'; + const selector = details.rawFilter.slice(prefix.length); + const isHtmlFilter = prefix.endsWith('^'); + const hostname = details.hostname; + + // The longer the needle, the lower the number of false positives. + // https://github.com/uBlockOrigin/uBlock-issues/issues/1139 + // Mind that there is no guarantee a selector has `\w` characters. + const needle = selector.match(/\w+|\*/g).reduce(function(a, b) { + return a.length > b.length ? a : b; + }); + + const regexFromLabels = (prefix, hn, suffix) => + new RegExp( + prefix + + hn.split('.').reduce((acc, item) => `(${acc}\\.)?${item}`) + + suffix + ); + + // https://github.com/uBlockOrigin/uBlock-issues/issues/803 + // Support looking up selectors of the form `*##...` + const reHostname = regexFromLabels('^', hostname, '$'); + let reEntity; + { + const domain = details.domain; + const pos = domain.indexOf('.'); + if ( pos !== -1 ) { + reEntity = regexFromLabels( + '^(', + hostname.slice(0, pos + hostname.length - domain.length), + '\\.)?\\*$' + ); + } + } + + const hostnameMatches = hn => { + return hn === '' || + reHostname.test(hn) || + reEntity !== undefined && reEntity.test(hn); + }; + + const response = Object.create(null); + + for ( const assetKey in listEntries ) { + const entry = listEntries[assetKey]; + if ( entry === undefined ) { continue; } + if ( entry.extendedContent === undefined ) { + entry.extendedContent = extractBlocks( + entry.content, + 'COSMETIC_FILTERS:SPECIFIC', + 'COSMETIC_FILTERS:GENERIC', + 'SCRIPTLET_FILTERS', + 'HTML_FILTERS', + 'HTTPHEADER_FILTERS' + ); + } + const content = entry.extendedContent; + let found; + let pos = 0; + while ( (pos = content.indexOf(needle, pos)) !== -1 ) { + let beg = content.lastIndexOf('\n', pos); + if ( beg === -1 ) { beg = 0; } + let end = content.indexOf('\n', pos); + if ( end === -1 ) { end = content.length; } + pos = end; + const fargs = JSON.parse(content.slice(beg, end)); + const filterType = fargs[0]; + + // https://github.com/gorhill/uBlock/issues/2763 + if ( + filterType >= 0 && + filterType <= 5 && + details.ignoreGeneric + ) { + continue; + } + + // Do not confuse cosmetic filters with HTML ones. + if ( (filterType === 64) !== isHtmlFilter ) { continue; } + + switch ( filterType ) { + // Lowly generic cosmetic filters + case 0: // simple id-based + if ( exception ) { break; } + if ( fargs[1] !== selector.slice(1) ) { break; } + if ( selector.charAt(0) !== '#' ) { break; } + found = prefix + selector; + break; + case 2: // simple class-based + if ( exception ) { break; } + if ( fargs[1] !== selector.slice(1) ) { break; } + if ( selector.charAt(0) !== '.' ) { break; } + found = prefix + selector; + break; + case 1: // complex id-based + case 3: // complex class-based + if ( exception ) { break; } + if ( fargs[2] !== selector ) { break; } + found = prefix + selector; + break; + // Highly generic cosmetic filters + case 4: // simple highly generic + case 5: // complex highly generic + if ( exception ) { break; } + if ( fargs[1] !== selector ) { break; } + found = prefix + selector; + break; + // Specific cosmetic filtering + // Generic exception + case 8: + // HTML filtering + // Response header filtering + case 64: { + if ( exception !== ((fargs[2] & 0b001) !== 0) ) { break; } + const isProcedural = (fargs[2] & 0b010) !== 0; + if ( + isProcedural === false && fargs[3] !== selector || + isProcedural && JSON.parse(fargs[3]).raw !== selector + ) { + break; + } + if ( hostnameMatches(fargs[1]) === false ) { break; } + // https://www.reddit.com/r/uBlockOrigin/comments/d6vxzj/ + // Ignore match if specific cosmetic filters are disabled + if ( + filterType === 8 && + exception === false && + details.ignoreSpecific + ) { + break; + } + found = fargs[1] + prefix + selector; + break; + } + // Scriptlet injection + case 32: + if ( exception !== ((fargs[2] & 0b001) !== 0) ) { break; } + if ( fargs[3] !== selector ) { break; } + if ( hostnameMatches(fargs[1]) ) { + found = fargs[1] + prefix + selector; + } + break; + } + if ( found !== undefined ) { + if ( response[found] === undefined ) { + response[found] = []; + } + response[found].push({ + assetKey: assetKey, + title: entry.title, + supportURL: entry.supportURL + }); + break; + } + } + } + + self.postMessage({ id: details.id, response }); +}; + +/******************************************************************************/ + +self.onmessage = function(e) { + const msg = e.data; + + switch ( msg.what ) { + case 'resetLists': + listEntries = Object.create(null); + break; + + case 'setList': + listEntries[msg.details.assetKey] = msg.details; + break; + + case 'fromNetFilter': + fromNetFilter(msg); + break; + + case 'fromExtendedFilter': + fromExtendedFilter(msg); + break; + } +}; + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/reverselookup.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/reverselookup.js new file mode 100644 index 0000000..5ffe41b --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/reverselookup.js @@ -0,0 +1,213 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-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 +*/ + +'use strict'; + +/******************************************************************************/ + +import staticNetFilteringEngine from './static-net-filtering.js'; +import µb from './background.js'; +import { CompiledListWriter } from './static-filtering-io.js'; +import { StaticFilteringParser } from './static-filtering-parser.js'; + +import { + domainFromHostname, + hostnameFromURI, +} from './uri-utils.js'; + +/******************************************************************************/ + +const workerTTL = 5 * 60 * 1000; +const pendingResponses = new Map(); + +let worker = null; +let workerTTLTimer; +let needLists = true; +let messageId = 1; + +const onWorkerMessage = function(e) { + const msg = e.data; + const resolver = pendingResponses.get(msg.id); + pendingResponses.delete(msg.id); + resolver(msg.response); +}; + +const stopWorker = function() { + if ( workerTTLTimer !== undefined ) { + clearTimeout(workerTTLTimer); + workerTTLTimer = undefined; + } + if ( worker === null ) { return; } + worker.terminate(); + worker = null; + needLists = true; + for ( const resolver of pendingResponses.values() ) { + resolver(); + } + pendingResponses.clear(); +}; + +const initWorker = function() { + if ( worker === null ) { + worker = new Worker('js/reverselookup-worker.js'); + worker.onmessage = onWorkerMessage; + } + + // The worker will be shutdown after n minutes without being used. + if ( workerTTLTimer !== undefined ) { + clearTimeout(workerTTLTimer); + } + workerTTLTimer = vAPI.setTimeout(stopWorker, workerTTL); + + if ( needLists === false ) { + return Promise.resolve(); + } + needLists = false; + + const entries = new Map(); + + const onListLoaded = function(details) { + const entry = entries.get(details.assetKey); + + // https://github.com/gorhill/uBlock/issues/536 + // Use assetKey when there is no filter list title. + + worker.postMessage({ + what: 'setList', + details: { + assetKey: details.assetKey, + title: entry.title || details.assetKey, + supportURL: entry.supportURL, + content: details.content + } + }); + }; + + for ( const listKey in µb.availableFilterLists ) { + if ( µb.availableFilterLists.hasOwnProperty(listKey) === false ) { + continue; + } + const entry = µb.availableFilterLists[listKey]; + if ( entry.off === true ) { continue; } + entries.set(listKey, { + title: listKey !== µb.userFiltersPath ? + entry.title : + vAPI.i18n('1pPageName'), + supportURL: entry.supportURL || '' + }); + } + if ( entries.size === 0 ) { + return Promise.resolve(); + } + + const promises = []; + for ( const listKey of entries.keys() ) { + promises.push( + µb.getCompiledFilterList(listKey).then(details => { + onListLoaded(details); + }) + ); + } + return Promise.all(promises); +}; + +const fromNetFilter = async function(rawFilter) { + if ( typeof rawFilter !== 'string' || rawFilter === '' ) { return; } + + const writer = new CompiledListWriter(); + const parser = new StaticFilteringParser(); + parser.setMaxTokenLength(staticNetFilteringEngine.MAX_TOKEN_LENGTH); + parser.analyze(rawFilter); + + const compiler = staticNetFilteringEngine.createCompiler(parser); + if ( compiler.compile(writer) === false ) { return; } + + await initWorker(); + + const id = messageId++; + worker.postMessage({ + what: 'fromNetFilter', + id: id, + compiledFilter: writer.last(), + rawFilter: rawFilter + }); + + return new Promise(resolve => { + pendingResponses.set(id, resolve); + }); +}; + +const fromExtendedFilter = async function(details) { + if ( + typeof details.rawFilter !== 'string' || + details.rawFilter === '' + ) { + return; + } + + await initWorker(); + + const id = messageId++; + const hostname = hostnameFromURI(details.url); + + worker.postMessage({ + what: 'fromExtendedFilter', + id: id, + domain: domainFromHostname(hostname), + hostname: hostname, + ignoreGeneric: + staticNetFilteringEngine.matchRequestReverse( + 'generichide', + details.url + ) === 2, + ignoreSpecific: + staticNetFilteringEngine.matchRequestReverse( + 'specifichide', + details.url + ) === 2, + rawFilter: details.rawFilter + }); + + return new Promise(resolve => { + pendingResponses.set(id, resolve); + }); +}; + +// This tells the worker that filter lists may have changed. + +const resetLists = function() { + needLists = true; + if ( worker === null ) { return; } + worker.postMessage({ what: 'resetLists' }); +}; + +/******************************************************************************/ + +const staticFilteringReverseLookup = { + fromNetFilter, + fromExtendedFilter, + resetLists, + shutdown: stopWorker +}; + +export default staticFilteringReverseLookup; + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlet-filtering.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlet-filtering.js new file mode 100644 index 0000000..1e6c3b1 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlet-filtering.js @@ -0,0 +1,455 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2017-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 +*/ + +'use strict'; + +/******************************************************************************/ + +import logger from './logger.js'; +import µb from './background.js'; +import { redirectEngine } from './redirect-engine.js'; +import { sessionFirewall } from './filtering-engines.js'; + +import { + StaticExtFilteringHostnameDB, + StaticExtFilteringSessionDB, +} from './static-ext-filtering-db.js'; + +import { + domainFromHostname, + entityFromDomain, + hostnameFromURI, +} from './uri-utils.js'; + +/******************************************************************************/ + +const duplicates = new Set(); +const scriptletCache = new µb.MRUCache(32); +const reEscapeScriptArg = /[\\'"]/g; + +const scriptletDB = new StaticExtFilteringHostnameDB(1); +const sessionScriptletDB = new StaticExtFilteringSessionDB(); + +let acceptedCount = 0; +let discardedCount = 0; + +const scriptletFilteringEngine = { + get acceptedCount() { + return acceptedCount; + }, + get discardedCount() { + return discardedCount; + }, + getFilterCount() { + return scriptletDB.size; + }, +}; + +// Purpose of `contentscriptCode` below is too programmatically inject +// content script code which only purpose is to inject scriptlets. This +// essentially does the same as what uBO's declarative content script does, +// except that this allows to inject the scriptlets earlier than it is +// possible through the declarative content script. +// +// Declaratively: +// 1. Browser injects generic content script => +// 2. Content script queries scriptlets => +// 3. Main process sends scriptlets => +// 4. Content script injects scriptlets +// +// Programmatically: +// 1. uBO injects specific scriptlets-aware content script => +// 2. Content script injects scriptlets +// +// However currently this programmatic injection works well only on +// Chromium-based browsers, it does not work properly with Firefox. More +// investigations is needed to find out why this fails with Firefox. +// Consequently, the programmatic-injection code path is taken only with +// Chromium-based browsers. + +const contentscriptCode = (( ) => { + const parts = [ + '(', + function(hostname, scriptlets) { + if ( + document.location === null || + hostname !== document.location.hostname + ) { + return; + } + const injectScriptlets = function(d) { + let script; + try { + script = d.createElement('script'); + script.appendChild(d.createTextNode( + decodeURIComponent(scriptlets)) + ); + (d.head || d.documentElement).appendChild(script); + } catch (ex) { + } + if ( script ) { + if ( script.parentNode ) { + script.parentNode.removeChild(script); + } + script.textContent = ''; + } + }; + injectScriptlets(document); + }.toString(), + ')(', + '"', 'hostname-slot', '", ', + '"', 'scriptlets-slot', '"', + ');', + '\n0;', + ]; + return { + parts: parts, + hostnameSlot: parts.indexOf('hostname-slot'), + scriptletsSlot: parts.indexOf('scriptlets-slot'), + assemble: function(hostname, scriptlets) { + this.parts[this.hostnameSlot] = hostname; + this.parts[this.scriptletsSlot] = + encodeURIComponent(scriptlets); + return this.parts.join(''); + } + }; +})(); + +// TODO: Probably should move this into StaticFilteringParser +// https://github.com/uBlockOrigin/uBlock-issues/issues/1031 +// Normalize scriptlet name to its canonical, unaliased name. +const normalizeRawFilter = function(rawFilter) { + const rawToken = rawFilter.slice(4, -1); + const rawEnd = rawToken.length; + let end = rawToken.indexOf(','); + if ( end === -1 ) { end = rawEnd; } + const token = rawToken.slice(0, end).trim(); + const alias = token.endsWith('.js') ? token.slice(0, -3) : token; + let normalized = redirectEngine.aliases.get(`${alias}.js`); + normalized = normalized === undefined + ? alias + : normalized.slice(0, -3); + let beg = end + 1; + while ( beg < rawEnd ) { + end = rawToken.indexOf(',', beg); + if ( end === -1 ) { end = rawEnd; } + normalized += ', ' + rawToken.slice(beg, end).trim(); + beg = end + 1; + } + return `+js(${normalized})`; +}; + +const lookupScriptlet = function(rawToken, reng, toInject) { + if ( toInject.has(rawToken) ) { return; } + if ( scriptletCache.resetTime < reng.modifyTime ) { + scriptletCache.reset(); + } + let content = scriptletCache.lookup(rawToken); + if ( content === undefined ) { + const pos = rawToken.indexOf(','); + let token, args; + if ( pos === -1 ) { + token = rawToken; + } else { + token = rawToken.slice(0, pos).trim(); + args = rawToken.slice(pos + 1).trim(); + } + // TODO: The alias lookup can be removed once scriptlet resources + // with obsolete name are converted to their new name. + if ( reng.aliases.has(token) ) { + token = reng.aliases.get(token); + } else { + token = `${token}.js`; + } + content = reng.resourceContentFromName( + token, + 'application/javascript' + ); + if ( !content ) { return; } + if ( args ) { + content = patchScriptlet(content, args); + if ( !content ) { return; } + } + content = + 'try {\n' + + content + '\n' + + '} catch ( e ) { }'; + scriptletCache.add(rawToken, content); + } + toInject.set(rawToken, content); +}; + +// Fill-in scriptlet argument placeholders. +const patchScriptlet = function(content, args) { + let s = args; + let len = s.length; + let beg = 0, pos = 0; + let i = 1; + while ( beg < len ) { + pos = s.indexOf(',', pos); + // Escaped comma? If so, skip. + if ( pos > 0 && s.charCodeAt(pos - 1) === 0x5C /* '\\' */ ) { + s = s.slice(0, pos - 1) + s.slice(pos); + len -= 1; + continue; + } + if ( pos === -1 ) { pos = len; } + content = content.replace( + `{{${i}}}`, + s.slice(beg, pos).trim().replace(reEscapeScriptArg, '\\$&') + ); + beg = pos = pos + 1; + i++; + } + return content; +}; + +const logOne = function(tabId, url, filter) { + µb.filteringContext + .duplicate() + .fromTabId(tabId) + .setRealm('extended') + .setType('dom') + .setURL(url) + .setDocOriginFromURL(url) + .setFilter({ source: 'extended', raw: filter }) + .toLogger(); +}; + +scriptletFilteringEngine.reset = function() { + scriptletDB.clear(); + duplicates.clear(); + acceptedCount = 0; + discardedCount = 0; +}; + +scriptletFilteringEngine.freeze = function() { + duplicates.clear(); + scriptletDB.collectGarbage(); +}; + +scriptletFilteringEngine.compile = function(parser, writer) { + writer.select('SCRIPTLET_FILTERS'); + + // Only exception filters are allowed to be global. + const { raw, exception } = parser.result; + const normalized = normalizeRawFilter(raw); + + // Tokenless is meaningful only for exception filters. + if ( normalized === '+js()' && exception === false ) { return; } + + if ( parser.hasOptions() === false ) { + if ( exception ) { + writer.push([ 32, '', 1, normalized ]); + } + return; + } + + // https://github.com/gorhill/uBlock/issues/3375 + // Ignore instances of exception filter with negated hostnames, + // because there is no way to create an exception to an exception. + + for ( const { hn, not, bad } of parser.extOptions() ) { + if ( bad ) { continue; } + let kind = 0; + if ( exception ) { + if ( not ) { continue; } + kind |= 1; + } else if ( not ) { + kind |= 1; + } + writer.push([ 32, hn, kind, normalized ]); + } +}; + +scriptletFilteringEngine.compileTemporary = function(parser) { + return { + session: sessionScriptletDB, + selector: parser.result.compiled, + }; +}; + +// 01234567890123456789 +// +js(token[, arg[, ...]]) +// ^ ^ +// 4 -1 + +scriptletFilteringEngine.fromCompiledContent = function(reader) { + reader.select('SCRIPTLET_FILTERS'); + + while ( reader.next() ) { + acceptedCount += 1; + const fingerprint = reader.fingerprint(); + if ( duplicates.has(fingerprint) ) { + discardedCount += 1; + continue; + } + duplicates.add(fingerprint); + const args = reader.args(); + if ( args.length < 4 ) { continue; } + scriptletDB.store(args[1], args[2], args[3].slice(4, -1)); + } +}; + +scriptletFilteringEngine.getSession = function() { + return sessionScriptletDB; +}; + +const $scriptlets = new Set(); +const $exceptions = new Set(); +const $scriptletToCodeMap = new Map(); + +scriptletFilteringEngine.retrieve = function(request, options = {}) { + if ( scriptletDB.size === 0 ) { return; } + + const hostname = request.hostname; + + $scriptlets.clear(); + $exceptions.clear(); + + if ( sessionScriptletDB.isNotEmpty ) { + sessionScriptletDB.retrieve([ null, $exceptions ]); + } + scriptletDB.retrieve(hostname, [ $scriptlets, $exceptions ]); + const entity = request.entity !== '' + ? `${hostname.slice(0, -request.domain.length)}${request.entity}` + : '*'; + scriptletDB.retrieve(entity, [ $scriptlets, $exceptions ], 1); + if ( $scriptlets.size === 0 ) { return; } + + // https://github.com/gorhill/uBlock/issues/2835 + // Do not inject scriptlets if the site is under an `allow` rule. + if ( + µb.userSettings.advancedUserEnabled && + sessionFirewall.evaluateCellZY(hostname, hostname, '*') === 2 + ) { + return; + } + + const mustLog = Array.isArray(options.logEntries); + + // Wholly disable scriptlet injection? + if ( $exceptions.has('') ) { + if ( mustLog ) { + logOne(request.tabId, request.url, '#@#+js()'); + } + return; + } + + $scriptletToCodeMap.clear(); + for ( const token of $scriptlets ) { + lookupScriptlet(token, redirectEngine, $scriptletToCodeMap); + } + if ( $scriptletToCodeMap.size === 0 ) { return; } + + // Return an array of scriptlets, and log results if needed. + const out = []; + for ( const [ token, code ] of $scriptletToCodeMap ) { + const isException = $exceptions.has(token); + if ( isException === false ) { + out.push(code); + } + if ( mustLog === false ) { continue; } + if ( isException ) { + logOne(request.tabId, request.url, `#@#+js(${token})`); + } else { + options.logEntries.push({ + token: `##+js(${token})`, + tabId: request.tabId, + url: request.url, + }); + } + } + + if ( out.length === 0 ) { return; } + + if ( µb.hiddenSettings.debugScriptlets ) { + out.unshift('debugger;'); + } + + // https://github.com/uBlockOrigin/uBlock-issues/issues/156 + // Provide a private Map() object available for use by all + // scriptlets. + out.unshift( + '(function() {', + '// >>>> start of private namespace', + '' + ); + out.push( + '', + '// <<<< end of private namespace', + '})();' + ); + + return out.join('\n'); +}; + +scriptletFilteringEngine.hasScriptlet = function(hostname, exceptionBit, scriptlet) { + return scriptletDB.hasStr(hostname, exceptionBit, scriptlet); +}; + +scriptletFilteringEngine.injectNow = function(details) { + if ( typeof details.frameId !== 'number' ) { return; } + const request = { + tabId: details.tabId, + frameId: details.frameId, + url: details.url, + hostname: hostnameFromURI(details.url), + domain: undefined, + entity: undefined + }; + request.domain = domainFromHostname(request.hostname); + request.entity = entityFromDomain(request.domain); + const logEntries = logger.enabled ? [] : undefined; + const scriptlets = this.retrieve(request, { logEntries }); + if ( scriptlets === undefined ) { return; } + let code = contentscriptCode.assemble(request.hostname, scriptlets); + if ( µb.hiddenSettings.debugScriptletInjector ) { + code = 'debugger;\n' + code; + } + const promise = vAPI.tabs.executeScript(details.tabId, { + code, + frameId: details.frameId, + matchAboutBlank: true, + runAt: 'document_start', + }); + if ( logEntries === undefined ) { return; } + promise.then(results => { + if ( Array.isArray(results) === false || results[0] !== 0 ) { return; } + for ( const entry of logEntries ) { + logOne(entry.tabId, entry.url, entry.token); + } + }); +}; + +scriptletFilteringEngine.toSelfie = function() { + return scriptletDB.toSelfie(); +}; + +scriptletFilteringEngine.fromSelfie = function(selfie) { + scriptletDB.fromSelfie(selfie); +}; + +/******************************************************************************/ + +export default scriptletFilteringEngine; + +/******************************************************************************/ diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/cosmetic-logger.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/cosmetic-logger.js new file mode 100644 index 0000000..53a626e --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/cosmetic-logger.js @@ -0,0 +1,386 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-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 +*/ + +'use strict'; + +/******************************************************************************/ + +(( ) => { +// >>>>>>>> start of private namespace + +/******************************************************************************/ + +if ( + typeof vAPI !== 'object' || + vAPI.domWatcher instanceof Object === false +) { + return; +} + +const reHasCSSCombinators = /[ >+~]/; +const simpleDeclarativeSet = new Set(); +let simpleDeclarativeStr; +const complexDeclarativeSet = new Set(); +let complexDeclarativeStr; +const declarativeStyleDict = new Map(); +let declarativeStyleStr; +const proceduralDict = new Map(); +const exceptionDict = new Map(); +let exceptionStr; +const proceduralExceptionDict = new Map(); +const nodesToProcess = new Set(); +const loggedSelectors = new Set(); + +/******************************************************************************/ + +const rePseudoElements = /:(?::?after|:?before|:[a-z-]+)$/; + +const safeMatchSelector = function(selector, context) { + const safeSelector = rePseudoElements.test(selector) + ? selector.replace(rePseudoElements, '') + : selector; + return context.matches(safeSelector); +}; + +const safeQuerySelector = function(selector, context = document) { + const safeSelector = rePseudoElements.test(selector) + ? selector.replace(rePseudoElements, '') + : selector; + return context.querySelector(safeSelector); +}; + +const safeGroupSelectors = function(selectors) { + const arr = Array.isArray(selectors) + ? selectors + : Array.from(selectors); + return arr.map(s => { + return rePseudoElements.test(s) + ? s.replace(rePseudoElements, '') + : s; + }).join(',\n'); +}; + +/******************************************************************************/ + +const processDeclarativeSimple = function(node, out) { + if ( simpleDeclarativeSet.size === 0 ) { return; } + if ( simpleDeclarativeStr === undefined ) { + simpleDeclarativeStr = safeGroupSelectors(simpleDeclarativeSet); + } + if ( + (node === document || node.matches(simpleDeclarativeStr) === false) && + (node.querySelector(simpleDeclarativeStr) === null) + ) { + return; + } + for ( const selector of simpleDeclarativeSet ) { + if ( + (node === document || safeMatchSelector(selector, node) === false) && + (safeQuerySelector(selector, node) === null) + ) { + continue; + } + out.push(`##${selector}`); + simpleDeclarativeSet.delete(selector); + simpleDeclarativeStr = undefined; + loggedSelectors.add(selector); + } +}; + +/******************************************************************************/ + +const processDeclarativeComplex = function(out) { + if ( complexDeclarativeSet.size === 0 ) { return; } + if ( complexDeclarativeStr === undefined ) { + complexDeclarativeStr = safeGroupSelectors(complexDeclarativeSet); + } + if ( document.querySelector(complexDeclarativeStr) === null ) { return; } + for ( const selector of complexDeclarativeSet ) { + if ( safeQuerySelector(selector) === null ) { continue; } + out.push(`##${selector}`); + complexDeclarativeSet.delete(selector); + complexDeclarativeStr = undefined; + loggedSelectors.add(selector); + } +}; + +/******************************************************************************/ + +const processDeclarativeStyle = function(out) { + if ( declarativeStyleDict.size === 0 ) { return; } + if ( declarativeStyleStr === undefined ) { + declarativeStyleStr = safeGroupSelectors(declarativeStyleDict.keys()); + } + if ( document.querySelector(declarativeStyleStr) === null ) { return; } + for ( const selector of declarativeStyleDict.keys() ) { + if ( safeQuerySelector(selector) === null ) { continue; } + for ( const style of declarativeStyleDict.get(selector) ) { + const raw = `##${selector}:style(${style})`; + out.push(raw); + loggedSelectors.add(raw); + } + declarativeStyleDict.delete(selector); + declarativeStyleStr = undefined; + } +}; + +/******************************************************************************/ + +const processProcedural = function(out) { + if ( proceduralDict.size === 0 ) { return; } + for ( const [ raw, pselector ] of proceduralDict ) { + if ( pselector.hit === false ) { continue; } + out.push(`##${raw}`); + proceduralDict.delete(raw); + } +}; + +/******************************************************************************/ + +const processExceptions = function(out) { + if ( exceptionDict.size === 0 ) { return; } + if ( exceptionStr === undefined ) { + exceptionStr = safeGroupSelectors(exceptionDict.keys()); + } + if ( document.querySelector(exceptionStr) === null ) { return; } + for ( const [ selector, raw ] of exceptionDict ) { + if ( safeQuerySelector(selector) === null ) { continue; } + out.push(`#@#${raw}`); + exceptionDict.delete(selector); + exceptionStr = undefined; + loggedSelectors.add(raw); + } +}; + +/******************************************************************************/ + +const processProceduralExceptions = function(out) { + if ( proceduralExceptionDict.size === 0 ) { return; } + for ( const exception of proceduralExceptionDict.values() ) { + if ( exception.test() === false ) { continue; } + out.push(`#@#${exception.raw}`); + proceduralExceptionDict.delete(exception.raw); + } +}; + +/******************************************************************************/ + +const processTimer = new vAPI.SafeAnimationFrame(( ) => { + //console.time('dom logger/scanning for matches'); + processTimer.clear(); + if ( nodesToProcess.size === 0 ) { return; } + + if ( nodesToProcess.size !== 1 && nodesToProcess.has(document) ) { + nodesToProcess.clear(); + nodesToProcess.add(document); + } + + const toLog = []; + if ( simpleDeclarativeSet.size !== 0 ) { + for ( const node of nodesToProcess ) { + processDeclarativeSimple(node, toLog); + } + } + + processDeclarativeComplex(toLog); + processDeclarativeStyle(toLog); + processProcedural(toLog); + processExceptions(toLog); + processProceduralExceptions(toLog); + + nodesToProcess.clear(); + + if ( toLog.length === 0 ) { return; } + + const location = vAPI.effectiveSelf.location; + + vAPI.messaging.send('scriptlets', { + what: 'logCosmeticFilteringData', + frameURL: location.href, + frameHostname: location.hostname, + matchedSelectors: toLog, + }); + //console.timeEnd('dom logger/scanning for matches'); +}); + +/******************************************************************************/ + +const attributeObserver = new MutationObserver(mutations => { + if ( nodesToProcess.has(document) ) { return; } + for ( const mutation of mutations ) { + const node = mutation.target; + if ( node.nodeType !== 1 ) { continue; } + nodesToProcess.add(node); + } + if ( nodesToProcess.size !== 0 ) { + processTimer.start(100); + } +}); + +/******************************************************************************/ + +const handlers = { + onFiltersetChanged: function(changes) { + //console.time('dom logger/filterset changed'); + for ( const entry of (changes.declarative || []) ) { + for ( let selector of entry[0].split(',\n') ) { + if ( entry[1] !== 'display:none!important;' ) { + declarativeStyleStr = undefined; + const styles = declarativeStyleDict.get(selector); + if ( styles === undefined ) { + declarativeStyleDict.set(selector, [ entry[1] ]); + continue; + } + styles.push(entry[1]); + continue; + } + if ( loggedSelectors.has(selector) ) { continue; } + if ( reHasCSSCombinators.test(selector) ) { + complexDeclarativeSet.add(selector); + complexDeclarativeStr = undefined; + } else { + simpleDeclarativeSet.add(selector); + simpleDeclarativeStr = undefined; + } + } + } + if ( + Array.isArray(changes.procedural) && + changes.procedural.length !== 0 + ) { + for ( const selector of changes.procedural ) { + proceduralDict.set(selector.raw, selector); + } + } + if ( Array.isArray(changes.exceptions) ) { + for ( const selector of changes.exceptions ) { + if ( loggedSelectors.has(selector) ) { continue; } + if ( selector.charCodeAt(0) !== 0x7B /* '{' */ ) { + exceptionDict.set(selector, selector); + continue; + } + const details = JSON.parse(selector); + if ( + details.action !== undefined && + details.tasks === undefined && + details.action[0] === ':style' + ) { + exceptionDict.set(details.selector, details.raw); + continue; + } + proceduralExceptionDict.set( + details.raw, + vAPI.domFilterer.createProceduralFilter(details) + ); + } + exceptionStr = undefined; + } + nodesToProcess.clear(); + nodesToProcess.add(document); + processTimer.start(1); + //console.timeEnd('dom logger/filterset changed'); + }, + + onDOMCreated: function() { + if ( vAPI.domFilterer instanceof Object === false ) { + return shutdown(); + } + handlers.onFiltersetChanged(vAPI.domFilterer.getAllSelectors()); + vAPI.domFilterer.addListener(handlers); + attributeObserver.observe(document.body, { + attributes: true, + subtree: true + }); + }, + + onDOMChanged: function(addedNodes) { + if ( nodesToProcess.has(document) ) { return; } + for ( const node of addedNodes ) { + if ( node.parentNode === null ) { continue; } + nodesToProcess.add(node); + } + if ( nodesToProcess.size !== 0 ) { + processTimer.start(100); + } + } +}; + +/******************************************************************************/ + +const shutdown = function() { + processTimer.clear(); + attributeObserver.disconnect(); + if ( typeof vAPI !== 'object' ) { return; } + if ( vAPI.domFilterer instanceof Object ) { + vAPI.domFilterer.removeListener(handlers); + } + if ( vAPI.domWatcher instanceof Object ) { + vAPI.domWatcher.removeListener(handlers); + } + if ( vAPI.broadcastListener instanceof Object ) { + vAPI.broadcastListener.remove(broadcastListener); + } +}; + +/******************************************************************************/ + +const broadcastListener = msg => { + if ( msg.what === 'loggerDisabled' ) { + shutdown(); + } +}; + +/******************************************************************************/ + +vAPI.messaging.extend().then(extended => { + if ( extended !== true ) { + return shutdown(); + } + vAPI.broadcastListener.add(broadcastListener); +}); + +vAPI.domWatcher.addListener(handlers); + +/******************************************************************************/ + +// <<<<<<<< end of private namespace +})(); + + + + + + + + +/******************************************************************************* + + DO NOT: + - Remove the following code + - Add code beyond the following code + Reason: + - https://github.com/gorhill/uBlock/pull/3721 + - uBO never uses the return value from injected content scripts + +**/ + +void 0; + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/cosmetic-off.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/cosmetic-off.js new file mode 100644 index 0000000..1b7b2ee --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/cosmetic-off.js @@ -0,0 +1,48 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-2018 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 +*/ + +'use strict'; + +/******************************************************************************/ + +if ( typeof vAPI === 'object' && vAPI.domFilterer ) { + vAPI.domFilterer.toggle(false); +} + + + + + + + + +/******************************************************************************* + + DO NOT: + - Remove the following code + - Add code beyond the following code + Reason: + - https://github.com/gorhill/uBlock/pull/3721 + - uBO never uses the return value from injected content scripts + +**/ + +void 0; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/cosmetic-on.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/cosmetic-on.js new file mode 100644 index 0000000..18c7478 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/cosmetic-on.js @@ -0,0 +1,48 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-2018 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 +*/ + +'use strict'; + +/******************************************************************************/ + +if ( typeof vAPI === 'object' && vAPI.domFilterer ) { + vAPI.domFilterer.toggle(true); +} + + + + + + + + +/******************************************************************************* + + DO NOT: + - Remove the following code + - Add code beyond the following code + Reason: + - https://github.com/gorhill/uBlock/pull/3721 + - uBO never uses the return value from injected content scripts + +**/ + +void 0; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/dom-inspector.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/dom-inspector.js new file mode 100644 index 0000000..c1edb90 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/dom-inspector.js @@ -0,0 +1,1008 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-2018 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 +*/ + +'use strict'; + +/******************************************************************************/ +/******************************************************************************/ + +(( ) => { + +/******************************************************************************/ + +if ( typeof vAPI !== 'object' || !vAPI.domFilterer ) { return; } + +/******************************************************************************/ + +var sessionId = vAPI.sessionId; + +if ( document.querySelector('iframe.dom-inspector.' + sessionId) !== null ) { + return; +} + +/******************************************************************************/ +/******************************************************************************/ + +// Modified to avoid installing as a global shim -- so the scriptlet can be +// flushed from memory once no longer in use. + +// Added serializeAsString parameter. + +/*! http://mths.be/cssescape v0.2.1 by @mathias | MIT license */ +const cssEscape = (function(/*root*/) { + + var InvalidCharacterError = function(message) { + this.message = message; + }; + InvalidCharacterError.prototype = new Error(); + InvalidCharacterError.prototype.name = 'InvalidCharacterError'; + + // http://dev.w3.org/csswg/cssom/#serialize-an-identifier + return function(value, serializeAsString) { + var string = String(value); + var length = string.length; + var index = -1; + var codeUnit; + var result = ''; + var firstCodeUnit = string.charCodeAt(0); + while (++index < length) { + codeUnit = string.charCodeAt(index); + // Note: there’s no need to special-case astral symbols, surrogate + // pairs, or lone surrogates. + + // If the character is NULL (U+0000), then throw an + // `InvalidCharacterError` exception and terminate these steps. + if (codeUnit === 0x0000) { + throw new InvalidCharacterError( + 'Invalid character: the input contains U+0000.' + ); + } + + if ( + // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is + // U+007F, […] + (codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit === 0x007F || + // If the character is the first character and is in the range [0-9] + // (U+0030 to U+0039), […] + (index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) || + // If the character is the second character and is in the range [0-9] + // (U+0030 to U+0039) and the first character is a `-` (U+002D), […] + ( + index === 1 && + codeUnit >= 0x0030 && codeUnit <= 0x0039 && + firstCodeUnit === 0x002D + ) + ) { + // http://dev.w3.org/csswg/cssom/#escape-a-character-as-code-point + result += '\\' + codeUnit.toString(16) + ' '; + continue; + } + + // If the character is not handled by one of the above rules and is + // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or + // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to + // U+005A), or [a-z] (U+0061 to U+007A), […] + if ( + codeUnit >= 0x0080 || + codeUnit === 0x002D || + codeUnit === 0x005F || + codeUnit >= 0x0030 && codeUnit <= 0x0039 || + codeUnit >= 0x0041 && codeUnit <= 0x005A || + codeUnit >= 0x0061 && codeUnit <= 0x007A + ) { + // the character itself + result += string.charAt(index); + continue; + } + + // If "serialize a string": + // If the character is '"' (U+0022) or "\" (U+005C), the escaped + // character. Otherwise, the character itself. + // http://dev.w3.org/csswg/cssom/#serialize-a-string + if ( serializeAsString && codeUnit !== 0x0022 && codeUnit !== 0x005C ) { + // the character itself + result += string.charAt(index); + continue; + } + + // Otherwise, the escaped character. + // http://dev.w3.org/csswg/cssom/#escape-a-character + result += '\\' + string.charAt(index); + + } + return result; + }; +}(self)); + +/******************************************************************************/ +/******************************************************************************/ + +let loggerConnectionId; + +// Highlighter-related +let svgRoot = null; +let pickerRoot = null; + +let nodeToIdMap = new WeakMap(); // No need to iterate + +let blueNodes = []; +const roRedNodes = new Map(); // node => current cosmetic filter +const rwRedNodes = new Set(); // node => new cosmetic filter (toggle node) +//var roGreenNodes = new Map(); // node => current exception cosmetic filter (can't toggle) +const rwGreenNodes = new Set(); // node => new exception cosmetic filter (toggle filter) + +const reHasCSSCombinators = /[ >+~]/; + +/******************************************************************************/ + +const domLayout = (function() { + const skipTagNames = new Set([ + 'br', 'head', 'link', 'meta', 'script', 'style', 'title' + ]); + const resourceAttrNames = new Map([ + [ 'a', 'href' ], + [ 'iframe', 'src' ], + [ 'img', 'src' ], + [ 'object', 'data' ] + ]); + + var idGenerator = 0; + + // This will be used to uniquely identify nodes across process. + + const newNodeId = function(node) { + var nid = 'n' + (idGenerator++).toString(36); + nodeToIdMap.set(node, nid); + return nid; + }; + + const selectorFromNode = function(node) { + var str, attr, pos, sw, i; + var tag = node.localName; + var selector = cssEscape(tag); + // Id + if ( typeof node.id === 'string' ) { + str = node.id.trim(); + if ( str !== '' ) { + selector += '#' + cssEscape(str); + } + } + // Class + var cl = node.classList; + if ( cl ) { + for ( i = 0; i < cl.length; i++ ) { + selector += '.' + cssEscape(cl[i]); + } + } + // Tag-specific attributes + attr = resourceAttrNames.get(tag); + if ( attr !== undefined ) { + str = node.getAttribute(attr) || ''; + str = str.trim(); + if ( str.startsWith('data:') ) { + pos = 5; + } else { + pos = str.search(/[#?]/); + } + if ( pos !== -1 ) { + str = str.slice(0, pos); + sw = '^'; + } else { + sw = ''; + } + if ( str !== '' ) { + selector += '[' + attr + sw + '="' + cssEscape(str, true) + '"]'; + } + } + return selector; + }; + + const DomRoot = function() { + this.nid = newNodeId(document.body); + this.lvl = 0; + this.sel = 'body'; + this.cnt = 0; + this.filter = roRedNodes.get(document.body); + }; + + const DomNode = function(node, level) { + this.nid = newNodeId(node); + this.lvl = level; + this.sel = selectorFromNode(node); + this.cnt = 0; + this.filter = roRedNodes.get(node); + }; + + const domNodeFactory = function(level, node) { + const localName = node.localName; + if ( skipTagNames.has(localName) ) { return null; } + // skip uBlock's own nodes + if ( node.classList.contains(sessionId) ) { return null; } + if ( level === 0 && localName === 'body' ) { + return new DomRoot(); + } + return new DomNode(node, level); + }; + + // Collect layout data. + + const getLayoutData = function() { + const layout = []; + const stack = []; + let lvl = 0; + let node = document.documentElement; + if ( node === null ) { return layout; } + + for (;;) { + const domNode = domNodeFactory(lvl, node); + if ( domNode !== null ) { + layout.push(domNode); + } + // children + if ( node.firstElementChild !== null ) { + stack.push(node); + lvl += 1; + node = node.firstElementChild; + continue; + } + // sibling + if ( node instanceof Element ) { + if ( node.nextElementSibling === null ) { + do { + node = stack.pop(); + if ( !node ) { break; } + lvl -= 1; + } while ( node.nextElementSibling === null ); + if ( !node ) { break; } + } + node = node.nextElementSibling; + } + } + + return layout; + }; + + // Descendant count for each node. + + const patchLayoutData = function(layout) { + var stack = [], ptr; + var lvl = 0; + var domNode, cnt; + var i = layout.length; + + while ( i-- ) { + domNode = layout[i]; + if ( domNode.lvl === lvl ) { + stack[ptr] += 1; + continue; + } + if ( domNode.lvl > lvl ) { + while ( lvl < domNode.lvl ) { + stack.push(0); + lvl += 1; + } + ptr = lvl - 1; + stack[ptr] += 1; + continue; + } + // domNode.lvl < lvl + cnt = stack.pop(); + domNode.cnt = cnt; + lvl -= 1; + ptr = lvl - 1; + stack[ptr] += cnt + 1; + } + return layout; + }; + + // Track and report mutations of the DOM + + var mutationObserver = null; + var mutationTimer; + var addedNodelists = []; + var removedNodelist = []; + + const previousElementSiblingId = function(node) { + var sibling = node; + for (;;) { + sibling = sibling.previousElementSibling; + if ( sibling === null ) { return null; } + if ( skipTagNames.has(sibling.localName) ) { continue; } + return nodeToIdMap.get(sibling); + } + }; + + const journalFromBranch = function(root, newNodes, newNodeToIdMap) { + var domNode; + var node = root.firstElementChild; + while ( node !== null ) { + domNode = domNodeFactory(undefined, node); + if ( domNode !== null ) { + newNodeToIdMap.set(domNode.nid, domNode); + newNodes.push(node); + } + // down + if ( node.firstElementChild !== null ) { + node = node.firstElementChild; + continue; + } + // right + if ( node.nextElementSibling !== null ) { + node = node.nextElementSibling; + continue; + } + // up then right + for (;;) { + if ( node.parentElement === root ) { return; } + node = node.parentElement; + if ( node.nextElementSibling !== null ) { + node = node.nextElementSibling; + break; + } + } + } + }; + + const journalFromMutations = function() { + var nodelist, node, domNode, nid; + mutationTimer = undefined; + + // This is used to temporarily hold all added nodes, before resolving + // their node id and relative position. + var newNodes = []; + var journalEntries = []; + var newNodeToIdMap = new Map(); + + for ( nodelist of addedNodelists ) { + for ( node of nodelist ) { + if ( node.nodeType !== 1 ) { continue; } + if ( node.parentElement === null ) { continue; } + cosmeticFilterMapper.incremental(node); + domNode = domNodeFactory(undefined, node); + if ( domNode !== null ) { + newNodeToIdMap.set(domNode.nid, domNode); + newNodes.push(node); + } + journalFromBranch(node, newNodes, newNodeToIdMap); + } + } + addedNodelists = []; + for ( nodelist of removedNodelist ) { + for ( node of nodelist ) { + if ( node.nodeType !== 1 ) { continue; } + nid = nodeToIdMap.get(node); + if ( nid === undefined ) { continue; } + journalEntries.push({ + what: -1, + nid: nid + }); + } + } + removedNodelist = []; + for ( node of newNodes ) { + journalEntries.push({ + what: 1, + nid: nodeToIdMap.get(node), + u: nodeToIdMap.get(node.parentElement), + l: previousElementSiblingId(node) + }); + } + + if ( journalEntries.length === 0 ) { return; } + + vAPI.MessagingConnection.sendTo(loggerConnectionId, { + what: 'domLayoutIncremental', + url: window.location.href, + hostname: window.location.hostname, + journal: journalEntries, + nodes: Array.from(newNodeToIdMap) + }); + }; + + const onMutationObserved = function(mutationRecords) { + for ( var record of mutationRecords ) { + if ( record.addedNodes.length !== 0 ) { + addedNodelists.push(record.addedNodes); + } + if ( record.removedNodes.length !== 0 ) { + removedNodelist.push(record.removedNodes); + } + } + if ( mutationTimer === undefined ) { + mutationTimer = vAPI.setTimeout(journalFromMutations, 1000); + } + }; + + // API + + const getLayout = function() { + cosmeticFilterMapper.reset(); + mutationObserver = new MutationObserver(onMutationObserved); + mutationObserver.observe(document.body, { + childList: true, + subtree: true + }); + + return { + what: 'domLayoutFull', + url: window.location.href, + hostname: window.location.hostname, + layout: patchLayoutData(getLayoutData()) + }; + }; + + const reset = function() { + shutdown(); + }; + + const shutdown = function() { + if ( mutationTimer !== undefined ) { + clearTimeout(mutationTimer); + mutationTimer = undefined; + } + if ( mutationObserver !== null ) { + mutationObserver.disconnect(); + mutationObserver = null; + } + addedNodelists = []; + removedNodelist = []; + nodeToIdMap = new WeakMap(); + }; + + return { + get: getLayout, + reset: reset, + shutdown: shutdown + }; +})(); + +// https://www.youtube.com/watch?v=qo8zKhd4Cf0 + +/******************************************************************************/ +/******************************************************************************/ + +// For browsers not supporting `:scope`, it's not the end of the world: the +// suggested CSS selectors may just end up being more verbose. + +let cssScope = ':scope > '; +try { + document.querySelector(':scope *'); +} catch (e) { + cssScope = ''; +} + +/******************************************************************************/ + +const cosmeticFilterMapper = (function() { + // https://github.com/gorhill/uBlock/issues/546 + var matchesFnName; + if ( typeof document.body.matches === 'function' ) { + matchesFnName = 'matches'; + } else if ( typeof document.body.mozMatchesSelector === 'function' ) { + matchesFnName = 'mozMatchesSelector'; + } else if ( typeof document.body.webkitMatchesSelector === 'function' ) { + matchesFnName = 'webkitMatchesSelector'; + } + + const nodesFromStyleTag = function(rootNode) { + const filterMap = roRedNodes; + const details = vAPI.domFilterer.getAllSelectors(); + + // Declarative selectors. + for ( const entry of (details.declarative || []) ) { + for ( const selector of entry[0].split(',\n') ) { + let canonical = selector; + let nodes; + if ( entry[1] !== vAPI.hideStyle ) { + canonical += ':style(' + entry[1] + ')'; + } + if ( reHasCSSCombinators.test(selector) ) { + nodes = document.querySelectorAll(selector); + } else { + if ( + filterMap.has(rootNode) === false && + rootNode[matchesFnName](selector) + ) { + filterMap.set(rootNode, canonical); + } + nodes = rootNode.querySelectorAll(selector); + } + for ( const node of nodes ) { + if ( filterMap.has(node) === false ) { + filterMap.set(node, canonical); + } + } + } + } + + // Procedural selectors. + for ( const entry of (details.procedural || []) ) { + const nodes = entry.exec(); + for ( const node of nodes ) { + // Upgrade declarative selector to procedural one + filterMap.set(node, entry.raw); + } + } + }; + + const incremental = function(rootNode) { + nodesFromStyleTag(rootNode); + }; + + const reset = function() { + roRedNodes.clear(); + if ( document.documentElement !== null ) { + incremental(document.documentElement); + } + }; + + const shutdown = function() { + vAPI.domFilterer.toggle(true); + }; + + return { + incremental: incremental, + reset: reset, + shutdown: shutdown + }; +})(); + +/******************************************************************************/ + +const elementsFromSelector = function(selector, context) { + if ( !context ) { + context = document; + } + if ( selector.indexOf(':') !== -1 ) { + const out = elementsFromSpecialSelector(selector); + if ( out !== undefined ) { return out; } + } + // plain CSS selector + try { + return context.querySelectorAll(selector); + } catch (ex) { + } + return []; +}; + +const elementsFromSpecialSelector = function(selector) { + var out = [], i; + var matches = /^(.+?):has\((.+?)\)$/.exec(selector); + if ( matches !== null ) { + var nodes; + try { + nodes = document.querySelectorAll(matches[1]); + } catch(ex) { + nodes = []; + } + i = nodes.length; + while ( i-- ) { + var node = nodes[i]; + if ( node.querySelector(matches[2]) !== null ) { + out.push(node); + } + } + return out; + } + + matches = /^:xpath\((.+?)\)$/.exec(selector); + if ( matches === null ) { return; } + const xpr = document.evaluate( + matches[1], + document, + null, + XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, + null + ); + i = xpr.snapshotLength; + while ( i-- ) { + out.push(xpr.snapshotItem(i)); + } + return out; +}; + +/******************************************************************************/ + +const getSvgRootChildren = function() { + if ( svgRoot.children ) { + return svgRoot.children; + } else { + const childNodes = Array.prototype.slice.apply(svgRoot.childNodes); + return childNodes.filter(function(node) { + return node.nodeType === Node.ELEMENT_NODE; + }); + } +}; + +const highlightElements = function() { + var islands; + var elem, rect, poly; + var xl, xr, yt, yb, w, h, ws; + var svgRootChildren = getSvgRootChildren(); + + islands = []; + for ( elem of rwRedNodes.keys() ) { + if ( elem === pickerRoot ) { continue; } + if ( rwGreenNodes.has(elem) ) { continue; } + if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; } + rect = elem.getBoundingClientRect(); + xl = rect.left; + xr = rect.right; + w = rect.width; + yt = rect.top; + yb = rect.bottom; + h = rect.height; + ws = w.toFixed(1); + poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) + + 'h' + ws + + 'v' + h.toFixed(1) + + 'h-' + ws + + 'z'; + islands.push(poly); + } + svgRootChildren[0].setAttribute('d', islands.join('') || 'M0 0'); + + islands = []; + for ( elem of rwGreenNodes ) { + if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; } + rect = elem.getBoundingClientRect(); + xl = rect.left; + xr = rect.right; + w = rect.width; + yt = rect.top; + yb = rect.bottom; + h = rect.height; + ws = w.toFixed(1); + poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) + + 'h' + ws + + 'v' + h.toFixed(1) + + 'h-' + ws + + 'z'; + islands.push(poly); + } + svgRootChildren[1].setAttribute('d', islands.join('') || 'M0 0'); + + islands = []; + for ( elem of roRedNodes.keys() ) { + if ( elem === pickerRoot ) { continue; } + if ( rwGreenNodes.has(elem) ) { continue; } + if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; } + rect = elem.getBoundingClientRect(); + xl = rect.left; + xr = rect.right; + w = rect.width; + yt = rect.top; + yb = rect.bottom; + h = rect.height; + ws = w.toFixed(1); + poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) + + 'h' + ws + + 'v' + h.toFixed(1) + + 'h-' + ws + + 'z'; + islands.push(poly); + } + svgRootChildren[2].setAttribute('d', islands.join('') || 'M0 0'); + + islands = []; + for ( elem of blueNodes ) { + if ( elem === pickerRoot ) { continue; } + if ( typeof elem.getBoundingClientRect !== 'function' ) { continue; } + rect = elem.getBoundingClientRect(); + xl = rect.left; + xr = rect.right; + w = rect.width; + yt = rect.top; + yb = rect.bottom; + h = rect.height; + ws = w.toFixed(1); + poly = 'M' + xl.toFixed(1) + ' ' + yt.toFixed(1) + + 'h' + ws + + 'v' + h.toFixed(1) + + 'h-' + ws + + 'z'; + islands.push(poly); + } + svgRootChildren[3].setAttribute('d', islands.join('') || 'M0 0'); +}; + +/******************************************************************************/ + +const onScrolled = (function() { + let buffered = false; + const timerHandler = function() { + buffered = false; + highlightElements(); + }; + return function() { + if ( buffered === false ) { + window.requestAnimationFrame(timerHandler); + buffered = true; + } + }; +})(); + +/******************************************************************************/ + +const selectNodes = function(selector, nid) { + const nodes = elementsFromSelector(selector); + if ( nid === '' ) { return nodes; } + for ( const node of nodes ) { + if ( nodeToIdMap.get(node) === nid ) { + return [ node ]; + } + } + return []; +}; + +/******************************************************************************/ + +const nodesFromFilter = function(selector) { + const out = []; + for ( const entry of roRedNodes ) { + if ( entry[1] === selector ) { + out.push(entry[0]); + } + } + return out; +}; + +/******************************************************************************/ + +const toggleExceptions = function(nodes, targetState) { + for ( const node of nodes ) { + if ( targetState ) { + rwGreenNodes.add(node); + } else { + rwGreenNodes.delete(node); + } + } +}; + +const toggleFilter = function(nodes, targetState) { + for ( const node of nodes ) { + if ( targetState ) { + rwRedNodes.delete(node); + } else { + rwRedNodes.add(node); + } + } +}; + +const resetToggledNodes = function() { + rwGreenNodes.clear(); + rwRedNodes.clear(); +}; + +/******************************************************************************/ + +const start = function() { + const onReady = function(ev) { + if ( ev ) { + document.removeEventListener(ev.type, onReady); + } + vAPI.MessagingConnection.sendTo(loggerConnectionId, domLayout.get()); + vAPI.domFilterer.toggle(false, highlightElements); + }; + if ( document.readyState === 'loading' ) { + document.addEventListener('DOMContentLoaded', onReady); + } else { + onReady(); + } +}; + +/******************************************************************************/ + +const shutdown = function() { + cosmeticFilterMapper.shutdown(); + domLayout.shutdown(); + vAPI.MessagingConnection.disconnectFrom(loggerConnectionId); + window.removeEventListener('scroll', onScrolled, true); + pickerRoot.remove(); + pickerRoot = svgRoot = null; +}; + +/******************************************************************************/ +/******************************************************************************/ + +const onMessage = function(request) { + var response, + nodes; + + switch ( request.what ) { + case 'commitFilters': + highlightElements(); + break; + + case 'domLayout': + response = domLayout.get(); + highlightElements(); + break; + + case 'highlightMode': + //svgRoot.classList.toggle('invert', request.invert); + break; + + case 'highlightOne': + blueNodes = selectNodes(request.selector, request.nid); + highlightElements(); + break; + + case 'resetToggledNodes': + resetToggledNodes(); + highlightElements(); + break; + + case 'showCommitted': + blueNodes = []; + // TODO: show only the new filters and exceptions. + highlightElements(); + break; + + case 'showInteractive': + blueNodes = []; + highlightElements(); + break; + + case 'toggleFilter': + nodes = selectNodes(request.selector, request.nid); + if ( nodes.length !== 0 ) { nodes[0].scrollIntoView(); } + toggleExceptions(nodesFromFilter(request.filter), request.target); + highlightElements(); + break; + + case 'toggleNodes': + nodes = selectNodes(request.selector, request.nid); + if ( nodes.length !== 0 ) { nodes[0].scrollIntoView(); } + toggleFilter(nodes, request.target); + highlightElements(); + break; + + default: + break; + } + + return response; +}; + +/******************************************************************************/ + +// Install DOM inspector widget + +const bootstrap = function(ev) { + if ( ev ) { + pickerRoot.removeEventListener(ev.type, bootstrap); + } + const pickerDoc = ev.target.contentDocument; + + const style = pickerDoc.createElement('style'); + style.textContent = [ + 'body {', + 'background-color: transparent;', + '}', + 'svg {', + 'height: 100%;', + 'left: 0;', + 'position: fixed;', + 'top: 0;', + 'width: 100%;', + '}', + 'svg > path:nth-of-type(1) {', + 'fill: rgba(255,0,0,0.2);', + 'stroke: #F00;', + '}', + 'svg > path:nth-of-type(2) {', + 'fill: rgba(0,255,0,0.2);', + 'stroke: #0F0;', + '}', + 'svg > path:nth-of-type(3) {', + 'fill: rgba(255,0,0,0.2);', + 'stroke: #F00;', + '}', + 'svg > path:nth-of-type(4) {', + 'fill: rgba(0,0,255,0.1);', + 'stroke: #FFF;', + 'stroke-width: 0.5px;', + '}', + '' + ].join('\n'); + pickerDoc.body.appendChild(style); + + svgRoot = pickerDoc.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svgRoot.appendChild(pickerDoc.createElementNS('http://www.w3.org/2000/svg', 'path')); + svgRoot.appendChild(pickerDoc.createElementNS('http://www.w3.org/2000/svg', 'path')); + svgRoot.appendChild(pickerDoc.createElementNS('http://www.w3.org/2000/svg', 'path')); + svgRoot.appendChild(pickerDoc.createElementNS('http://www.w3.org/2000/svg', 'path')); + pickerDoc.body.appendChild(svgRoot); + + window.addEventListener('scroll', onScrolled, true); + + // Dynamically add direct connection abilities so that we can establish + // a direct, fast messaging connection to the logger. + vAPI.messaging.extend().then(extended => { + if ( extended !== true ) { return; } + vAPI.MessagingConnection.connectTo('domInspector', 'loggerUI', msg => { + switch ( msg.what ) { + case 'connectionAccepted': + loggerConnectionId = msg.id; + start(); + break; + case 'connectionBroken': + shutdown(); + break; + case 'connectionMessage': + onMessage(msg.payload); + break; + } + }); + }); +}; + +pickerRoot = document.createElement('iframe'); +pickerRoot.classList.add(sessionId); +pickerRoot.classList.add('dom-inspector'); +pickerRoot.style.cssText = [ + 'background: transparent', + 'border: 0', + 'border-radius: 0', + 'box-shadow: none', + 'display: block', + 'height: 100%', + 'left: 0', + 'margin: 0', + 'opacity: 1', + 'position: fixed', + 'outline: 0', + 'padding: 0', + 'pointer-events:none;', + 'top: 0', + 'visibility: visible', + 'width: 100%', + 'z-index: 2147483647', + '' +].join(' !important;\n'); + +pickerRoot.addEventListener('load', ev => { bootstrap(ev); }); +(document.documentElement || document).appendChild(pickerRoot); + +/******************************************************************************/ + +})(); + + + + + + + + +/******************************************************************************* + + DO NOT: + - Remove the following code + - Add code beyond the following code + Reason: + - https://github.com/gorhill/uBlock/pull/3721 + - uBO never uses the return value from injected content scripts + +**/ + +void 0; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/dom-survey-elements.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/dom-survey-elements.js new file mode 100644 index 0000000..0386f38 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/dom-survey-elements.js @@ -0,0 +1,72 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-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 +*/ + +'use strict'; + +/******************************************************************************/ + +// https://github.com/uBlockOrigin/uBlock-issues/issues/756 +// Keep in mind CPU usage with large DOM and/or filterset. + +(( ) => { + if ( typeof vAPI !== 'object' ) { return; } + + const t0 = Date.now(); + + if ( vAPI.domSurveyElements instanceof Object === false ) { + vAPI.domSurveyElements = { + busy: false, + hiddenElementCount: Number.NaN, + surveyTime: t0, + }; + } + const surveyResults = vAPI.domSurveyElements; + + if ( surveyResults.busy ) { return; } + surveyResults.busy = true; + + if ( surveyResults.surveyTime < vAPI.domMutationTime ) { + surveyResults.hiddenElementCount = Number.NaN; + } + surveyResults.surveyTime = t0; + + if ( isNaN(surveyResults.hiddenElementCount) ) { + surveyResults.hiddenElementCount = (( ) => { + if ( vAPI.domFilterer instanceof Object === false ) { return 0; } + const details = vAPI.domFilterer.getAllSelectors(0b11); + if ( + Array.isArray(details.declarative) === false || + details.declarative.length === 0 + ) { + return 0; + } + return document.querySelectorAll( + details.declarative.map(entry => entry[0]).join(',') + ).length; + })(); + } + + surveyResults.busy = false; + + // IMPORTANT: This is returned to the injector, so this MUST be + // the last statement. + return surveyResults.hiddenElementCount; +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/dom-survey-scripts.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/dom-survey-scripts.js new file mode 100644 index 0000000..0f0f501 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/dom-survey-scripts.js @@ -0,0 +1,126 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-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 +*/ + +'use strict'; + +/******************************************************************************/ + +// Scriptlets to count the number of script tags in a document. + +(( ) => { + if ( typeof vAPI !== 'object' ) { return; } + + const t0 = Date.now(); + + if ( vAPI.domSurveyScripts instanceof Object === false ) { + vAPI.domSurveyScripts = { + busy: false, + scriptCount: -1, + surveyTime: t0, + }; + } + const surveyResults = vAPI.domSurveyScripts; + + if ( surveyResults.busy ) { return; } + surveyResults.busy = true; + + if ( surveyResults.surveyTime < vAPI.domMutationTime ) { + surveyResults.scriptCount = -1; + } + surveyResults.surveyTime = t0; + + if ( surveyResults.scriptCount === -1 ) { + const reInlineScript = /^(data:|blob:|$)/; + let inlineScriptCount = 0; + let scriptCount = 0; + for ( const script of document.scripts ) { + if ( reInlineScript.test(script.src) ) { + inlineScriptCount = 1; + continue; + } + scriptCount += 1; + if ( scriptCount === 99 ) { break; } + } + scriptCount += inlineScriptCount; + if ( scriptCount !== 0 ) { + surveyResults.scriptCount = scriptCount; + } + } + + // https://github.com/uBlockOrigin/uBlock-issues/issues/756 + // Keep trying to find inline script-like instances but only if we + // have the time-budget to do so. + if ( surveyResults.scriptCount === -1 ) { + if ( document.querySelector('a[href^="javascript:"]') !== null ) { + surveyResults.scriptCount = 1; + } + } + + // https://github.com/uBlockOrigin/uBlock-issues/issues/1756 + // Mind that there might be no body element. + if ( surveyResults.scriptCount === -1 && document.body !== null ) { + surveyResults.scriptCount = 0; + const onHandlers = new Set([ + 'onabort', 'onblur', 'oncancel', 'oncanplay', + 'oncanplaythrough', 'onchange', 'onclick', 'onclose', + 'oncontextmenu', 'oncuechange', 'ondblclick', 'ondrag', + 'ondragend', 'ondragenter', 'ondragexit', 'ondragleave', + 'ondragover', 'ondragstart', 'ondrop', 'ondurationchange', + 'onemptied', 'onended', 'onerror', 'onfocus', + 'oninput', 'oninvalid', 'onkeydown', 'onkeypress', + 'onkeyup', 'onload', 'onloadeddata', 'onloadedmetadata', + 'onloadstart', 'onmousedown', 'onmouseenter', 'onmouseleave', + 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', + 'onwheel', 'onpause', 'onplay', 'onplaying', + 'onprogress', 'onratechange', 'onreset', 'onresize', + 'onscroll', 'onseeked', 'onseeking', 'onselect', + 'onshow', 'onstalled', 'onsubmit', 'onsuspend', + 'ontimeupdate', 'ontoggle', 'onvolumechange', 'onwaiting', + 'onafterprint', 'onbeforeprint', 'onbeforeunload', 'onhashchange', + 'onlanguagechange', 'onmessage', 'onoffline', 'ononline', + 'onpagehide', 'onpageshow', 'onrejectionhandled', 'onpopstate', + 'onstorage', 'onunhandledrejection', 'onunload', + 'oncopy', 'oncut', 'onpaste' + ]); + const nodeIter = document.createNodeIterator( + document.body, + NodeFilter.SHOW_ELEMENT + ); + for (;;) { + const node = nodeIter.nextNode(); + if ( node === null ) { break; } + if ( node.hasAttributes() === false ) { continue; } + for ( const attr of node.getAttributeNames() ) { + if ( onHandlers.has(attr) === false ) { continue; } + surveyResults.scriptCount = 1; + break; + } + } + } + + surveyResults.busy = false; + + // IMPORTANT: This is returned to the injector, so this MUST be + // the last statement. + if ( surveyResults.scriptCount !== -1 ) { + return surveyResults.scriptCount; + } +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/epicker.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/epicker.js new file mode 100644 index 0000000..e288e78 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/epicker.js @@ -0,0 +1,1340 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +/* global CSS */ + +'use strict'; + +/******************************************************************************/ +/******************************************************************************/ + +(async ( ) => { + +/******************************************************************************/ + +if ( typeof vAPI !== 'object' || vAPI === null ) { + return; +} + +/******************************************************************************/ + +const epickerId = vAPI.randomToken(); +let epickerConnectionId; + +let pickerRoot = document.querySelector(`[${vAPI.sessionId}]`); +if ( pickerRoot !== null ) { return; } + +let pickerBootArgs; + +const reCosmeticAnchor = /^#[$?]?#/; + +const netFilterCandidates = []; +const cosmeticFilterCandidates = []; + +let targetElements = []; +let candidateElements = []; +let bestCandidateFilter = null; + +const lastNetFilterSession = window.location.host + window.location.pathname; +let lastNetFilterHostname = ''; +let lastNetFilterUnion = ''; + +const hideBackgroundStyle = 'background-image:none!important;'; + +/******************************************************************************/ + +const safeQuerySelectorAll = function(node, selector) { + if ( node !== null ) { + try { + return node.querySelectorAll(selector); + } catch (e) { + } + } + return []; +}; + +/******************************************************************************/ + +const getElementBoundingClientRect = function(elem) { + let rect = typeof elem.getBoundingClientRect === 'function' + ? elem.getBoundingClientRect() + : { height: 0, left: 0, top: 0, width: 0 }; + + // https://github.com/gorhill/uBlock/issues/1024 + // Try not returning an empty bounding rect. + if ( rect.width !== 0 && rect.height !== 0 ) { + return rect; + } + if ( elem.shadowRoot instanceof DocumentFragment ) { + return getElementBoundingClientRect(elem.shadowRoot); + } + + let left = rect.left, + right = left + rect.width, + top = rect.top, + bottom = top + rect.height; + + for ( const child of elem.children ) { + rect = getElementBoundingClientRect(child); + if ( rect.width === 0 || rect.height === 0 ) { continue; } + if ( rect.left < left ) { left = rect.left; } + if ( rect.right > right ) { right = rect.right; } + if ( rect.top < top ) { top = rect.top; } + if ( rect.bottom > bottom ) { bottom = rect.bottom; } + } + + return { + bottom, + height: bottom - top, + left, + right, + top, + width: right - left + }; +}; + +/******************************************************************************/ + +const highlightElements = function(elems, force) { + // To make mouse move handler more efficient + if ( + (force !== true) && + (elems.length === targetElements.length) && + (elems.length === 0 || elems[0] === targetElements[0]) + ) { + return; + } + targetElements = []; + + const ow = self.innerWidth; + const oh = self.innerHeight; + const islands = []; + + for ( const elem of elems ) { + if ( elem === pickerRoot ) { continue; } + targetElements.push(elem); + const rect = getElementBoundingClientRect(elem); + // Ignore offscreen areas + if ( + rect.left > ow || rect.top > oh || + rect.left + rect.width < 0 || rect.top + rect.height < 0 + ) { + continue; + } + islands.push( + `M${rect.left} ${rect.top}h${rect.width}v${rect.height}h-${rect.width}z` + ); + } + + vAPI.MessagingConnection.sendTo(epickerConnectionId, { + what: 'svgPaths', + ocean: `M0 0h${ow}v${oh}h-${ow}z`, + islands: islands.join(''), + }); +}; + +/******************************************************************************/ + +const mergeStrings = function(urls) { + if ( urls.length === 0 ) { return ''; } + if ( + urls.length === 1 || + self.diff_match_patch instanceof Function === false + ) { + return urls[0]; + } + const differ = new self.diff_match_patch(); + let merged = urls[0]; + for ( let i = 1; i < urls.length; i++ ) { + // The differ works at line granularity: we insert a linefeed after + // each character to trick the differ to work at character granularity. + const diffs = differ.diff_main( + urls[i].split('').join('\n'), + merged.split('').join('\n') + ); + const result = []; + for ( const diff of diffs ) { + if ( diff[0] !== 0 ) { + result.push('*'); + } else { + result.push(diff[1].replace(/\n+/g, '')); + } + merged = result.join(''); + } + } + // Keep usage of wildcards to a sane level, too many of them can cause + // high overhead filters + merged = merged.replace(/^\*+$/, '') + .replace(/\*{2,}/g, '*') + .replace(/([^*]{1,3}\*)(?:[^*]{1,3}\*)+/g, '$1'); + + // https://github.com/uBlockOrigin/uBlock-issues/issues/1494 + let pos = merged.indexOf('/'); + if ( pos === -1 ) { pos = merged.length; } + return merged.slice(0, pos).includes('*') ? urls[0] : merged; +}; + +/******************************************************************************/ + +// Remove fragment part from a URL. + +const trimFragmentFromURL = function(url) { + const pos = url.indexOf('#'); + return pos !== -1 ? url.slice(0, pos) : url; +}; + +/******************************************************************************/ + +// https://github.com/gorhill/uBlock/issues/1897 +// Ignore `data:` URI, they can't be handled by an HTTP observer. + +const backgroundImageURLFromElement = function(elem) { + const style = window.getComputedStyle(elem); + const bgImg = style.backgroundImage || ''; + const matches = /^url\((["']?)([^"']+)\1\)$/.exec(bgImg); + const url = matches !== null && matches.length === 3 ? matches[2] : ''; + return url.lastIndexOf('data:', 0) === -1 + ? trimFragmentFromURL(url.slice(0, 1024)) + : ''; +}; + +/******************************************************************************/ + +// https://github.com/gorhill/uBlock/issues/1725#issuecomment-226479197 +// Limit returned string to 1024 characters. +// Also, return only URLs which will be seen by an HTTP observer. + +const resourceURLsFromElement = function(elem) { + const urls = []; + const tagName = elem.localName; + const prop = netFilter1stSources[tagName]; + if ( prop === undefined ) { + const url = backgroundImageURLFromElement(elem); + if ( url !== '' ) { urls.push(url); } + return urls; + } + const s = elem[prop]; + if ( typeof s === 'string' && /^https?:\/\//.test(s) ) { + urls.push(trimFragmentFromURL(s.slice(0, 1024))); + } + resourceURLsFromSrcset(elem, urls); + resourceURLsFromPicture(elem, urls); + return urls; +}; + +// https://html.spec.whatwg.org/multipage/images.html#parsing-a-srcset-attribute +// https://github.com/uBlockOrigin/uBlock-issues/issues/1071 +const resourceURLsFromSrcset = function(elem, out) { + let srcset = elem.srcset; + if ( typeof srcset !== 'string' || srcset === '' ) { return; } + for(;;) { + // trim whitespace + srcset = srcset.trim(); + if ( srcset.length === 0 ) { break; } + // abort in case of leading comma + if ( /^,/.test(srcset) ) { break; } + // collect and consume all non-whitespace characters + let match = /^\S+/.exec(srcset); + if ( match === null ) { break; } + srcset = srcset.slice(match.index + match[0].length); + let url = match[0]; + // consume descriptor, if any + if ( /,$/.test(url) ) { + url = url.replace(/,$/, ''); + if ( /,$/.test(url) ) { break; } + } else { + match = /^[^,]*(?:\(.+?\))?[^,]*(?:,|$)/.exec(srcset); + if ( match === null ) { break; } + srcset = srcset.slice(match.index + match[0].length); + } + const parsedURL = new URL(url, document.baseURI); + if ( parsedURL.pathname.length === 0 ) { continue; } + out.push(trimFragmentFromURL(parsedURL.href)); + } +}; + +// https://github.com/uBlockOrigin/uBlock-issues/issues/2069#issuecomment-1080600661 +// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture +const resourceURLsFromPicture = function(elem, out) { + if ( elem.localName === 'source' ) { return; } + const picture = elem.parentElement; + if ( picture === null || picture.localName !== 'picture' ) { return; } + const sources = picture.querySelectorAll(':scope > source'); + for ( const source of sources ) { + const urls = resourceURLsFromElement(source); + if ( urls.length === 0 ) { continue; } + out.push(...urls); + } +}; + +/******************************************************************************/ + +const netFilterFromUnion = function(patternIn, out) { + // Reset reference filter when dealing with unrelated URLs + const currentHostname = self.location.hostname; + if ( + lastNetFilterUnion === '' || + currentHostname === '' || + currentHostname !== lastNetFilterHostname + ) { + lastNetFilterHostname = currentHostname; + lastNetFilterUnion = patternIn; + vAPI.messaging.send('elementPicker', { + what: 'elementPickerEprom', + lastNetFilterSession, + lastNetFilterHostname, + lastNetFilterUnion, + }); + return; + } + + // Related URLs + lastNetFilterHostname = currentHostname; + let patternOut = mergeStrings([ patternIn, lastNetFilterUnion ]); + if ( patternOut !== '/*' && patternOut !== patternIn ) { + const filter = `||${patternOut}`; + if ( out.indexOf(filter) === -1 ) { + out.push(filter); + } + lastNetFilterUnion = patternOut; + } + + // Remember across element picker sessions + vAPI.messaging.send('elementPicker', { + what: 'elementPickerEprom', + lastNetFilterSession, + lastNetFilterHostname, + lastNetFilterUnion, + }); +}; + +/******************************************************************************/ + +// Extract the best possible net filter, i.e. as specific as possible. + +const netFilterFromElement = function(elem) { + if ( elem === null ) { return 0; } + if ( elem.nodeType !== 1 ) { return 0; } + const urls = resourceURLsFromElement(elem); + if ( urls.length === 0 ) { return 0; } + + if ( candidateElements.indexOf(elem) === -1 ) { + candidateElements.push(elem); + } + + const candidates = netFilterCandidates; + const len = candidates.length; + + for ( let i = 0; i < urls.length; i++ ) { + urls[i] = urls[i].replace(/^https?:\/\//, ''); + } + const pattern = mergeStrings(urls); + + + if ( bestCandidateFilter === null && elem.matches('html,body') === false ) { + bestCandidateFilter = { + type: 'net', + filters: candidates, + slot: candidates.length + }; + } + + candidates.push(`||${pattern}`); + + // Suggest a less narrow filter if possible + const pos = pattern.indexOf('?'); + if ( pos !== -1 ) { + candidates.push(`||${pattern.slice(0, pos)}`); + } + + // Suggest a filter which is a result of combining more than one URL. + netFilterFromUnion(pattern, candidates); + + return candidates.length - len; +}; + +const netFilter1stSources = { + 'audio': 'src', + 'embed': 'src', + 'iframe': 'src', + 'img': 'src', + 'object': 'data', + 'source': 'src', + 'video': 'src' +}; + +const filterTypes = { + 'audio': 'media', + 'embed': 'object', + 'iframe': 'subdocument', + 'img': 'image', + 'object': 'object', + 'video': 'media', +}; + +/******************************************************************************/ + +// Extract the best possible cosmetic filter, i.e. as specific as possible. + +// https://github.com/gorhill/uBlock/issues/1725 +// Also take into account the `src` attribute for `img` elements -- and limit +// the value to the 1024 first characters. + +const cosmeticFilterFromElement = function(elem) { + if ( elem === null ) { return 0; } + if ( elem.nodeType !== 1 ) { return 0; } + if ( noCosmeticFiltering ) { return 0; } + + if ( candidateElements.indexOf(elem) === -1 ) { + candidateElements.push(elem); + } + + let selector = ''; + + // Id + let v = typeof elem.id === 'string' && CSS.escape(elem.id); + if ( v ) { + selector = '#' + v; + } + + // Class(es) + v = elem.classList; + if ( v ) { + let i = v.length || 0; + while ( i-- ) { + selector += '.' + CSS.escape(v.item(i)); + } + } + + // Tag name + const tagName = CSS.escape(elem.localName); + + // Use attributes if still no selector found. + // https://github.com/gorhill/uBlock/issues/1901 + // Trim attribute value, this may help in case of malformed HTML. + // + // https://github.com/uBlockOrigin/uBlock-issues/issues/1923 + // Escape unescaped `"` in attribute values + if ( selector === '' ) { + let attributes = [], attr; + switch ( tagName ) { + case 'a': + v = elem.getAttribute('href'); + if ( v ) { + v = v.trim().replace(/\?.*$/, ''); + if ( v.length ) { + attributes.push({ k: 'href', v: v }); + } + } + break; + case 'iframe': + case 'img': + v = elem.getAttribute('src'); + if ( v && v.length !== 0 ) { + v = v.trim(); + if ( v.startsWith('data:') ) { + let pos = v.indexOf(','); + if ( pos !== -1 ) { + v = v.slice(0, pos + 1); + } + } else if ( v.startsWith('blob:') ) { + v = new URL(v.slice(5)); + v.pathname = ''; + v = 'blob:' + v.href; + } + attributes.push({ k: 'src', v: v.slice(0, 256) }); + break; + } + v = elem.getAttribute('alt'); + if ( v && v.length !== 0 ) { + attributes.push({ k: 'alt', v: v }); + break; + } + break; + default: + break; + } + while ( (attr = attributes.pop()) ) { + if ( attr.v.length === 0 ) { continue; } + const w = attr.v.replace(/([^\\])"/g, '$1\\"'); + v = elem.getAttribute(attr.k); + if ( attr.v === v ) { + selector += `[${attr.k}="${w}"]`; + } else if ( v.startsWith(attr.v) ) { + selector += `[${attr.k}^="${w}"]`; + } else { + selector += `[${attr.k}*="${w}"]`; + } + } + } + + // https://github.com/uBlockOrigin/uBlock-issues/issues/17 + // If selector is ambiguous at this point, add the element name to + // further narrow it down. + const parentNode = elem.parentNode; + if ( + selector === '' || + safeQuerySelectorAll(parentNode, `:scope > ${selector}`).length > 1 + ) { + selector = tagName + selector; + } + + // https://github.com/chrisaljoudi/uBlock/issues/637 + // If the selector is still ambiguous at this point, further narrow using + // `nth-of-type`. It is preferable to use `nth-of-type` as opposed to + // `nth-child`, as `nth-of-type` is less volatile. + if ( safeQuerySelectorAll(parentNode, `:scope > ${selector}`).length > 1 ) { + let i = 1; + while ( elem.previousSibling !== null ) { + elem = elem.previousSibling; + if ( + typeof elem.localName === 'string' && + elem.localName === tagName + ) { + i++; + } + } + selector += `:nth-of-type(${i})`; + } + + if ( bestCandidateFilter === null ) { + bestCandidateFilter = { + type: 'cosmetic', + filters: cosmeticFilterCandidates, + slot: cosmeticFilterCandidates.length + }; + } + + cosmeticFilterCandidates.push(`##${selector}`); + + return 1; +}; + +/******************************************************************************/ + +const filtersFrom = function(x, y) { + bestCandidateFilter = null; + netFilterCandidates.length = 0; + cosmeticFilterCandidates.length = 0; + candidateElements.length = 0; + + // We need at least one element. + let first = null; + if ( typeof x === 'number' ) { + first = elementFromPoint(x, y); + } else if ( x instanceof HTMLElement ) { + first = x; + x = undefined; + } + + // https://github.com/gorhill/uBlock/issues/1545 + // Network filter candidates from all other elements found at [x,y]. + // https://www.reddit.com/r/uBlockOrigin/comments/qmjk36/ + // Extract network candidates first. + if ( typeof x === 'number' ) { + const magicAttr = `${vAPI.sessionId}-clickblind`; + pickerRoot.setAttribute(magicAttr, ''); + const elems = document.elementsFromPoint(x, y); + pickerRoot.removeAttribute(magicAttr); + for ( const elem of elems ) { + netFilterFromElement(elem); + } + } else if ( first !== null ) { + netFilterFromElement(first); + } + + // Cosmetic filter candidates from ancestors. + // https://github.com/gorhill/uBlock/issues/2519 + // https://github.com/uBlockOrigin/uBlock-issues/issues/17 + // Prepend `body` if full selector is ambiguous. + let elem = first; + while ( elem && elem !== document.body ) { + cosmeticFilterFromElement(elem); + elem = elem.parentNode; + } + // The body tag is needed as anchor only when the immediate child + // uses `nth-of-type`. + let i = cosmeticFilterCandidates.length; + if ( i !== 0 ) { + const selector = cosmeticFilterCandidates[i-1].slice(2); + if ( safeQuerySelectorAll(document.body, selector).length > 1 ) { + cosmeticFilterCandidates.push('##body'); + } + } + + // https://github.com/gorhill/uBlock/commit/ebaa8a8bb28aef043a68c99965fe6c128a3fe5e4#commitcomment-63818019 + // If still no best candidate, just use whatever is available in network + // filter candidates -- which may have been previously skipped in favor + // of cosmetic filters. + if ( bestCandidateFilter === null && netFilterCandidates.length !== 0 ) { + bestCandidateFilter = { + type: 'net', + filters: netFilterCandidates, + slot: 0 + }; + } + + return netFilterCandidates.length + cosmeticFilterCandidates.length; +}; + +/******************************************************************************* + + filterToDOMInterface.queryAll + @desc Look-up all the HTML elements matching the filter passed in + argument. + @param string, a cosmetic or network filter. + @param function, called once all items matching the filter have been + collected. + @return array, or undefined if the filter is invalid. + + filterToDOMInterface.preview + @desc Apply/unapply filter to the DOM. + @param string, a cosmetic of network filter, or literal false to remove + the effects of the filter on the DOM. + @return undefined. + + TODO: need to be revised once I implement chained cosmetic operators. + +*/ + +const filterToDOMInterface = (( ) => { + const reHnAnchorPrefix = '^[\\w-]+://(?:[^/?#]+\\.)?'; + const reCaret = '(?:[^%.0-9a-z_-]|$)'; + const rePseudoElements = /:(?::?after|:?before|:[a-z-]+)$/; + + // Net filters: we need to lookup manually -- translating into a foolproof + // CSS selector is just not possible. + // + // https://github.com/chrisaljoudi/uBlock/issues/945 + // Transform into a regular expression, this allows the user to + // edit and insert wildcard(s) into the proposed filter. + // https://www.reddit.com/r/uBlockOrigin/comments/c5do7w/ + // Better handling of pure hostname filters. Also, discard single + // alphanumeric character filters. + const fromNetworkFilter = function(filter) { + const out = []; + if ( /^[0-9a-z]$/i.test(filter) ) { return out; } + let reStr = ''; + if ( + filter.length > 2 && + filter.startsWith('/') && + filter.endsWith('/') + ) { + reStr = filter.slice(1, -1); + } else if ( /^\w[\w.-]*[a-z]$/i.test(filter) ) { + reStr = reHnAnchorPrefix + + filter.toLowerCase().replace(/\./g, '\\.') + + reCaret; + } else { + let rePrefix = '', reSuffix = ''; + if ( filter.startsWith('||') ) { + rePrefix = reHnAnchorPrefix; + filter = filter.slice(2); + } else if ( filter.startsWith('|') ) { + rePrefix = '^'; + filter = filter.slice(1); + } + if ( filter.endsWith('|') ) { + reSuffix = '$'; + filter = filter.slice(0, -1); + } + reStr = rePrefix + + filter.replace(/[.+?${}()|[\]\\]/g, '\\$&') + .replace(/\*+/g, '.*') + .replace(/\^/g, reCaret) + + reSuffix; + } + let reFilter = null; + try { + reFilter = new RegExp(reStr, 'i'); + } + catch (e) { + return out; + } + + // Lookup by tag names. + const elems = document.querySelectorAll( + Object.keys(netFilter1stSources).join() + ); + for ( const elem of elems ) { + const srcProp = netFilter1stSources[elem.localName]; + const src = elem[srcProp]; + if ( + typeof src === 'string' && + reFilter.test(src) || + typeof elem.currentSrc === 'string' && + reFilter.test(elem.currentSrc) + ) { + out.push({ + elem, + src: srcProp, + opt: filterTypes[elem.localName], + style: vAPI.hideStyle, + }); + } + } + + // Find matching background image in current set of candidate elements. + for ( const elem of candidateElements ) { + if ( reFilter.test(backgroundImageURLFromElement(elem)) ) { + out.push({ + elem, + bg: true, + opt: 'image', + style: hideBackgroundStyle, + }); + } + } + + return out; + }; + + // Cosmetic filters: these are straight CSS selectors. + // + // https://github.com/uBlockOrigin/uBlock-issues/issues/389 + // Test filter using comma-separated list to better detect invalid CSS + // selectors. + // + // https://github.com/gorhill/uBlock/issues/2515 + // Remove trailing pseudo-element when querying. + const fromPlainCosmeticFilter = function(raw) { + let elems; + try { + document.documentElement.matches(`${raw},\na`); + elems = document.querySelectorAll( + raw.replace(rePseudoElements, '') + ); + } + catch (e) { + return; + } + const out = []; + for ( const elem of elems ) { + if ( elem === pickerRoot ) { continue; } + out.push({ elem, raw, style: vAPI.hideStyle }); + } + return out; + }; + + // https://github.com/gorhill/uBlock/issues/1772 + // Handle procedural cosmetic filters. + // + // https://github.com/gorhill/uBlock/issues/2515 + // Remove trailing pseudo-element when querying. + const fromCompiledCosmeticFilter = function(raw) { + if ( noCosmeticFiltering ) { return; } + if ( typeof raw !== 'string' ) { return; } + let elems, style; + try { + const o = JSON.parse(raw); + elems = vAPI.domFilterer.createProceduralFilter(o).exec(); + style = o.action === undefined || o.action[0] !== ':style' + ? vAPI.hideStyle + : o.action[1]; + } catch(ex) { + return; + } + if ( !elems ) { return; } + const out = []; + for ( const elem of elems ) { + out.push({ elem, raw, style }); + } + return out; + }; + + vAPI.epickerStyleProxies = vAPI.epickerStyleProxies || new Map(); + + let lastFilter; + let lastResultset; + let previewing = false; + + const queryAll = function(details) { + let { filter, compiled } = details; + filter = filter.trim(); + if ( filter === lastFilter ) { return lastResultset; } + unapply(); + if ( filter === '' || filter === '!' ) { + lastFilter = ''; + lastResultset = undefined; + return; + } + lastFilter = filter; + if ( reCosmeticAnchor.test(filter) === false ) { + lastResultset = fromNetworkFilter(filter); + if ( previewing ) { apply(); } + return lastResultset; + } + lastResultset = fromPlainCosmeticFilter(compiled); + if ( lastResultset ) { + if ( previewing ) { apply(); } + return lastResultset; + } + // Procedural cosmetic filter + lastResultset = fromCompiledCosmeticFilter(compiled); + if ( previewing ) { apply(); } + return lastResultset; + }; + + const apply = function() { + unapply(); + if ( Array.isArray(lastResultset) === false ) { return; } + const rootElem = document.documentElement; + for ( const { elem, style } of lastResultset ) { + if ( elem === pickerRoot ) { continue; } + if ( elem === rootElem && style === vAPI.hideStyle ) { continue; } + let styleToken = vAPI.epickerStyleProxies.get(style); + if ( styleToken === undefined ) { + styleToken = vAPI.randomToken(); + vAPI.epickerStyleProxies.set(style, styleToken); + vAPI.userStylesheet.add(`[${styleToken}]\n{${style}}`, true); + } + elem.setAttribute(styleToken, ''); + } + }; + + const unapply = function() { + for ( const styleToken of vAPI.epickerStyleProxies.values() ) { + for ( const elem of document.querySelectorAll(`[${styleToken}]`) ) { + elem.removeAttribute(styleToken); + } + } + }; + + // https://www.reddit.com/r/uBlockOrigin/comments/c62irc/ + // Support injecting the cosmetic filters into the DOM filterer + // immediately rather than wait for the next page load. + const preview = function(state, permanent = false) { + previewing = state !== false; + if ( previewing === false ) { + return unapply(); + } + if ( Array.isArray(lastResultset) === false ) { return; } + if ( permanent === false || reCosmeticAnchor.test(lastFilter) === false ) { + return apply(); + } + if ( noCosmeticFiltering ) { return; } + const cssSelectors = new Set(); + const proceduralSelectors = new Set(); + for ( const { raw } of lastResultset ) { + if ( raw.startsWith('{') ) { + proceduralSelectors.add(raw); + } else { + cssSelectors.add(raw); + } + } + if ( cssSelectors.size !== 0 ) { + vAPI.domFilterer.addCSS( + `${Array.from(cssSelectors).join('\n')}\n{${vAPI.hideStyle}}`, + { mustInject: true } + ); + } + if ( proceduralSelectors.size !== 0 ) { + vAPI.domFilterer.addProceduralSelectors( + Array.from(proceduralSelectors) + ); + } + }; + + return { preview, queryAll }; +})(); + +/******************************************************************************/ + +const onOptmizeCandidates = function(details) { + const { candidates } = details; + const results = []; + for ( const paths of candidates ) { + let count = Number.MAX_SAFE_INTEGER; + let selector = ''; + for ( let i = 0, n = paths.length; i < n; i++ ) { + const s = paths.slice(n - i - 1).join(''); + const elems = document.querySelectorAll(s); + if ( elems.length < count ) { + selector = s; + count = elems.length; + } + } + results.push({ selector: `##${selector}`, count }); + } + // Sort by most match count and shortest selector to least match count and + // longest selector. + results.sort((a, b) => { + const r = b.count - a.count; + if ( r !== 0 ) { return r; } + return a.selector.length - b.selector.length; + }); + vAPI.MessagingConnection.sendTo(epickerConnectionId, { + what: 'candidatesOptimized', + candidates: results.map(a => a.selector), + slot: details.slot, + }); +}; + +/******************************************************************************/ + +const showDialog = function(options) { + vAPI.MessagingConnection.sendTo(epickerConnectionId, { + what: 'showDialog', + url: self.location.href, + netFilters: netFilterCandidates, + cosmeticFilters: cosmeticFilterCandidates, + filter: bestCandidateFilter, + options, + }); +}; + +/******************************************************************************/ + +const elementFromPoint = (( ) => { + let lastX, lastY; + + return (x, y) => { + if ( x !== undefined ) { + lastX = x; lastY = y; + } else if ( lastX !== undefined ) { + x = lastX; y = lastY; + } else { + return null; + } + if ( !pickerRoot ) { return null; } + const magicAttr = `${vAPI.sessionId}-clickblind`; + pickerRoot.setAttribute(magicAttr, ''); + let elem = document.elementFromPoint(x, y); + if ( + elem === null || /* to skip following tests */ + elem === document.body || + elem === document.documentElement || ( + pickerBootArgs.zap !== true && + noCosmeticFiltering && + resourceURLsFromElement(elem).length === 0 + ) + ) { + elem = null; + } + // https://github.com/uBlockOrigin/uBlock-issues/issues/380 + pickerRoot.removeAttribute(magicAttr); + return elem; + }; +})(); + +/******************************************************************************/ + +const highlightElementAtPoint = function(mx, my) { + const elem = elementFromPoint(mx, my); + highlightElements(elem ? [ elem ] : []); +}; + +/******************************************************************************/ + +const filterElementAtPoint = function(mx, my, broad) { + if ( filtersFrom(mx, my) === 0 ) { return; } + showDialog({ broad }); +}; + +/******************************************************************************/ + +// https://www.reddit.com/r/uBlockOrigin/comments/bktxtb/scrolling_doesnt_work/emn901o +// Override 'fixed' position property on body element if present. + +// With touch-driven devices, first highlight the element and remove only +// when tapping again the highlighted area. + +const zapElementAtPoint = function(mx, my, options) { + if ( options.highlight ) { + const elem = elementFromPoint(mx, my); + if ( elem ) { + highlightElements([ elem ]); + } + return; + } + + let elemToRemove = targetElements.length !== 0 && targetElements[0] || null; + if ( elemToRemove === null && mx !== undefined ) { + elemToRemove = elementFromPoint(mx, my); + } + + if ( elemToRemove instanceof Element === false ) { return; } + + const getStyleValue = (elem, prop) => { + const style = window.getComputedStyle(elem); + return style ? style[prop] : ''; + }; + + // Heuristic to detect scroll-locking: remove such lock when detected. + let maybeScrollLocked = elemToRemove.shadowRoot instanceof DocumentFragment; + if ( maybeScrollLocked === false ) { + let elem = elemToRemove; + do { + maybeScrollLocked = + parseInt(getStyleValue(elem, 'zIndex'), 10) >= 1000 || + getStyleValue(elem, 'position') === 'fixed'; + elem = elem.parentElement; + } while ( elem !== null && maybeScrollLocked === false ); + } + if ( maybeScrollLocked ) { + const doc = document; + if ( getStyleValue(doc.body, 'overflowY') === 'hidden' ) { + doc.body.style.setProperty('overflow', 'auto', 'important'); + } + if ( getStyleValue(doc.body, 'position') === 'fixed' ) { + doc.body.style.setProperty('position', 'initial', 'important'); + } + if ( getStyleValue(doc.documentElement, 'position') === 'fixed' ) { + doc.documentElement.style.setProperty('position', 'initial', 'important'); + } + if ( getStyleValue(doc.documentElement, 'overflowY') === 'hidden' ) { + doc.documentElement.style.setProperty('overflow', 'auto', 'important'); + } + } + elemToRemove.remove(); + highlightElementAtPoint(mx, my); +}; + +/******************************************************************************/ + +const onKeyPressed = function(ev) { + // Delete + if ( + (ev.key === 'Delete' || ev.key === 'Backspace') && + pickerBootArgs.zap + ) { + ev.stopPropagation(); + ev.preventDefault(); + zapElementAtPoint(); + return; + } + // Esc + if ( ev.key === 'Escape' || ev.which === 27 ) { + ev.stopPropagation(); + ev.preventDefault(); + filterToDOMInterface.preview(false); + quitPicker(); + return; + } +}; + +/******************************************************************************/ + +// https://github.com/chrisaljoudi/uBlock/issues/190 +// May need to dynamically adjust the height of the overlay + new position +// of highlighted elements. + +const onViewportChanged = function() { + highlightElements(targetElements, true); +}; + +/******************************************************************************/ + +// Auto-select a specific target, if any, and if possible + +const startPicker = function() { + pickerRoot.focus(); + + self.addEventListener('scroll', onViewportChanged, { passive: true }); + self.addEventListener('resize', onViewportChanged, { passive: true }); + self.addEventListener('keydown', onKeyPressed, true); + + // Try using mouse position + if ( + pickerBootArgs.mouse && + vAPI.mouseClick instanceof Object && + typeof vAPI.mouseClick.x === 'number' && + vAPI.mouseClick.x > 0 + ) { + if ( filtersFrom(vAPI.mouseClick.x, vAPI.mouseClick.y) !== 0 ) { + return showDialog(); + } + } + + // No mouse position available, use suggested target + const target = pickerBootArgs.target || ''; + const pos = target.indexOf('\t'); + if ( pos === -1 ) { return; } + + const srcAttrMap = { + 'a': 'href', + 'audio': 'src', + 'embed': 'src', + 'iframe': 'src', + 'img': 'src', + 'video': 'src', + }; + const tagName = target.slice(0, pos); + const url = target.slice(pos + 1); + const attr = srcAttrMap[tagName]; + if ( attr === undefined ) { return; } + const elems = document.getElementsByTagName(tagName); + for ( const elem of elems ) { + if ( elem === pickerRoot ) { continue; } + const srcs = resourceURLsFromElement(elem); + if ( + (srcs.length !== 0 && srcs.includes(url) === false) || + (srcs.length === 0 && url !== 'about:blank') + ) { + continue; + } + filtersFrom(elem); + if ( + netFilterCandidates.length !== 0 || + cosmeticFilterCandidates.length !== 0 + ) { + if ( pickerBootArgs.mouse !== true ) { + elem.scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center' + }); + } + showDialog({ broad: true }); + } + return; + } + + // A target was specified, but it wasn't found: abort. + quitPicker(); +}; + +/******************************************************************************/ + +// Let's have the element picker code flushed from memory when no longer +// in use: to ensure this, release all local references. + +const quitPicker = function() { + self.removeEventListener('scroll', onViewportChanged, { passive: true }); + self.removeEventListener('resize', onViewportChanged, { passive: true }); + self.removeEventListener('keydown', onKeyPressed, true); + vAPI.shutdown.remove(quitPicker); + vAPI.MessagingConnection.disconnectFrom(epickerConnectionId); + vAPI.MessagingConnection.removeListener(onConnectionMessage); + vAPI.userStylesheet.remove(pickerCSS); + vAPI.userStylesheet.apply(); + + if ( pickerRoot === null ) { return; } + + pickerRoot.remove(); + pickerRoot = null; + + self.focus(); +}; + +/******************************************************************************/ + +const onDialogMessage = function(msg) { + switch ( msg.what ) { + case 'start': + startPicker(); + if ( targetElements.length === 0 ) { + highlightElements([], true); + } + break; + case 'optimizeCandidates': + onOptmizeCandidates(msg); + break; + case 'dialogCreate': + filterToDOMInterface.queryAll(msg); + filterToDOMInterface.preview(true, true); + quitPicker(); + break; + case 'dialogSetFilter': { + const resultset = filterToDOMInterface.queryAll(msg) || []; + highlightElements(resultset.map(a => a.elem), true); + if ( msg.filter === '!' ) { break; } + vAPI.MessagingConnection.sendTo(epickerConnectionId, { + what: 'resultsetDetails', + count: resultset.length, + opt: resultset.length !== 0 ? resultset[0].opt : undefined, + }); + break; + } + case 'quitPicker': + filterToDOMInterface.preview(false); + quitPicker(); + break; + case 'highlightElementAtPoint': + highlightElementAtPoint(msg.mx, msg.my); + break; + case 'unhighlight': + highlightElements([]); + break; + case 'filterElementAtPoint': + filterElementAtPoint(msg.mx, msg.my, msg.broad); + break; + case 'zapElementAtPoint': + zapElementAtPoint(msg.mx, msg.my, msg.options); + if ( msg.options.highlight !== true && msg.options.stay !== true ) { + quitPicker(); + } + break; + case 'togglePreview': + filterToDOMInterface.preview(msg.state); + if ( msg.state === false ) { + highlightElements(targetElements, true); + } + break; + default: + break; + } +}; + +/******************************************************************************/ + +const onConnectionMessage = function(msg) { + if ( msg.from !== `epickerDialog-${epickerId}` ) { return; } + switch ( msg.what ) { + case 'connectionRequested': + epickerConnectionId = msg.id; + return true; + case 'connectionBroken': + quitPicker(); + break; + case 'connectionMessage': + onDialogMessage(msg.payload); + break; + } +}; + +/******************************************************************************/ + +// epicker-ui.html will be injected in the page through an iframe, and +// is a sandboxed so as to prevent the page from interfering with its +// content and behavior. +// +// The purpose of epicker.js is to: +// - Install the element picker UI, and wait for the component to establish +// a direct communication channel. +// - Lookup candidate filters from elements at a specific position. +// - Highlight element(s) at a specific position or according to whether +// they match candidate filters; +// - Preview the result of applying a candidate filter; +// +// When the element picker is installed on a page, the only change the page +// sees is an iframe with a random attribute. The page can't see the content +// of the iframe, and cannot interfere with its style properties. However the +// page can remove the iframe. + +// We need extra messaging capabilities + fetch/process picker arguments. +{ + const results = await Promise.all([ + vAPI.messaging.extend(), + vAPI.messaging.send('elementPicker', { what: 'elementPickerArguments' }), + ]); + if ( results[0] !== true ) { return; } + pickerBootArgs = results[1]; + if ( typeof pickerBootArgs !== 'object' || pickerBootArgs === null ) { + return; + } + // Restore net filter union data if origin is the same. + const eprom = pickerBootArgs.eprom || null; + if ( eprom !== null && eprom.lastNetFilterSession === lastNetFilterSession ) { + lastNetFilterHostname = eprom.lastNetFilterHostname || ''; + lastNetFilterUnion = eprom.lastNetFilterUnion || ''; + } +} + +// The DOM filterer will not be present when cosmetic filtering is disabled. +const noCosmeticFiltering = + vAPI.domFilterer instanceof Object === false || + vAPI.noSpecificCosmeticFiltering === true; + +// https://github.com/gorhill/uBlock/issues/1529 +// In addition to inline styles, harden the element picker styles by using +// dedicated CSS rules. +const pickerCSSStyle = [ + 'background: transparent', + 'border: 0', + 'border-radius: 0', + 'box-shadow: none', + 'display: block', + 'filter: none', + 'height: 100vh', + 'left: 0', + 'margin: 0', + 'max-height: none', + 'max-width: none', + 'min-height: unset', + 'min-width: unset', + 'opacity: 1', + 'outline: 0', + 'padding: 0', + 'pointer-events: auto', + 'position: fixed', + 'top: 0', + 'transform: none', + 'visibility: visible', + 'width: 100%', + 'z-index: 2147483647', + // https://github.com/uBlockOrigin/uBlock-issues/issues/1408 + 'color-scheme: light', + '' +]; + +const pickerCSS = ` +:root > [${vAPI.sessionId}] { + ${pickerCSSStyle.join(' !important;')} +} +:root [${vAPI.sessionId}-clickblind] { + pointer-events: none !important; +} +`; + +vAPI.userStylesheet.add(pickerCSS); +vAPI.userStylesheet.apply(); + +pickerRoot = document.createElement('iframe'); +pickerRoot.setAttribute(vAPI.sessionId, ''); +document.documentElement.append(pickerRoot); + +vAPI.shutdown.add(quitPicker); + +vAPI.MessagingConnection.addListener(onConnectionMessage); + +{ + const url = new URL(pickerBootArgs.pickerURL); + url.searchParams.set('epid', epickerId); + if ( pickerBootArgs.zap ) { + url.searchParams.set('zap', '1'); + } + pickerRoot.contentWindow.location = url.href; +} + +/******************************************************************************/ + +})(); + + + + + + + + +/******************************************************************************* + + DO NOT: + - Remove the following code + - Add code beyond the following code + Reason: + - https://github.com/gorhill/uBlock/pull/3721 + - uBO never uses the return value from injected content scripts + +**/ + +void 0; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/load-3p-css.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/load-3p-css.js new file mode 100644 index 0000000..92f402d --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/load-3p-css.js @@ -0,0 +1,67 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2020-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 +*/ + +'use strict'; + +/******************************************************************************/ + +(( ) => { + if ( typeof vAPI !== 'object' ) { return; } + + if ( vAPI.dynamicReloadToken === undefined ) { + vAPI.dynamicReloadToken = vAPI.randomToken(); + } + + for ( const sheet of Array.from(document.styleSheets) ) { + let loaded = false; + try { + loaded = sheet.rules.length !== 0; + } catch(ex) { + } + if ( loaded ) { continue; } + const link = sheet.ownerNode || null; + if ( link === null || link.localName !== 'link' ) { continue; } + if ( link.hasAttribute(vAPI.dynamicReloadToken) ) { continue; } + const clone = link.cloneNode(true); + clone.setAttribute(vAPI.dynamicReloadToken, ''); + link.replaceWith(clone); + } +})(); + + + + + + + + +/******************************************************************************* + + DO NOT: + - Remove the following code + - Add code beyond the following code + Reason: + - https://github.com/gorhill/uBlock/pull/3721 + - uBO never uses the return value from injected content scripts + +**/ + +void 0; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/load-large-media-all.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/load-large-media-all.js new file mode 100644 index 0000000..9d4c17f --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/load-large-media-all.js @@ -0,0 +1,62 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-2018 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 +*/ + +'use strict'; + +/******************************************************************************/ + +(( ) => { + +/******************************************************************************/ + +if ( + typeof vAPI !== 'object' || + vAPI.loadAllLargeMedia instanceof Function === false +) { + return; +} + +vAPI.loadAllLargeMedia(); +vAPI.loadAllLargeMedia = undefined; + +/******************************************************************************/ + +})(); + + + + + + + + +/******************************************************************************* + + DO NOT: + - Remove the following code + - Add code beyond the following code + Reason: + - https://github.com/gorhill/uBlock/pull/3721 + - uBO never uses the return value from injected content scripts + +**/ + +void 0; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/load-large-media-interactive.js b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/load-large-media-interactive.js new file mode 100644 index 0000000..12fa343 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/js/scriptlets/load-large-media-interactive.js @@ -0,0 +1,299 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2015-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 +*/ + +'use strict'; + +/******************************************************************************/ + +(( ) => { + +/******************************************************************************/ + +// This can happen +if ( typeof vAPI !== 'object' || vAPI.loadAllLargeMedia instanceof Function ) { + return; +} + +/******************************************************************************/ + +const largeMediaElementAttribute = 'data-' + vAPI.sessionId; +const largeMediaElementSelector = + ':root audio[' + largeMediaElementAttribute + '],\n' + + ':root img[' + largeMediaElementAttribute + '],\n' + + ':root picture[' + largeMediaElementAttribute + '],\n' + + ':root video[' + largeMediaElementAttribute + ']'; + +/******************************************************************************/ + +const isMediaElement = function(elem) { + return /^(?:audio|img|picture|video)$/.test(elem.localName); +}; + +/******************************************************************************/ + +const mediaNotLoaded = function(elem) { + switch ( elem.localName ) { + case 'audio': + case 'video': { + const src = elem.src || ''; + if ( src.startsWith('blob:') ) { + elem.autoplay = false; + elem.pause(); + } + return elem.readyState === 0 || elem.error !== null; + } + case 'img': { + if ( elem.naturalWidth !== 0 || elem.naturalHeight !== 0 ) { + break; + } + const style = window.getComputedStyle(elem); + // For some reason, style can be null with Pale Moon. + return style !== null ? + style.getPropertyValue('display') !== 'none' : + elem.offsetHeight !== 0 && elem.offsetWidth !== 0; + } + default: + break; + } + return false; +}; + +/******************************************************************************/ + +// For all media resources which have failed to load, trigger a reload. + +//
+ +
+
+
+
+ + + + + +
+
+
+
    +
    +
    +
    +
    +
    + + + + + + + + + angle-up +
    +
    +
    + +
    css/fontimagemediascript
    +
    xhrframedomother
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    00:00:00 ** 3,3inline-script 
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/managed_storage.json b/src/i2p.chromium.base.profile/extensions/ublock.js/managed_storage.json new file mode 100644 index 0000000..25614dd --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/managed_storage.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema#", + "type": "object", + "properties": { + "adminSettings": { + "title": "A valid JSON string compliant with uBO's backup format", + "description": "All entries present will overwrite local settings.", + "type": "string" + }, + "advancedSettings": { + "title": "A list of [name,value] pairs to populate advanced settings", + "type": "array", + "items": { + "title": "A [name,value] pair", + "type": "array", + "items": { "type": "string" } + } + }, + "userSettings": { + "title": "A list of [name,value] pairs to populate user settings", + "type": "array", + "items": { + "title": "A [name,value] pair", + "type": "array", + "items": { "type": "string" } + } + }, + "disableDashboard": { + "title": "Set to true to prevent access to configuration options", + "type": "boolean" + }, + "disabledPopupPanelParts": { + "title": "An array of strings used to remove parts of the popup panel", + "type": "array", + "items": { "type": "string" } + }, + "toAdd": { + "title": "Settings to add at launch time", + "type": "object", + "properties": { + "trustedSiteDirectives": { + "title": "A list of trusted-site directives", + "description": "Trusted-site directives to always add at launch time.", + "type": "array", + "items": { "type": "string" } + } + } + }, + "toOverwrite": { + "title": "Settings to overwrite at launch time", + "type": "object", + "properties": { + "filters": { + "title": "A collection of filters", + "description": "The set of user filters to use at launch time -- where each entry is a distinct line.", + "type": "array", + "items": { "type": "string" } + }, + "filterLists": { + "title": "A collection of list identifiers and/or list URLs", + "description": "The set of filter lists to use at launch time.", + "type": "array", + "items": { "type": "string" } + }, + "trustedSiteDirectives": { + "title": "A list of trusted-site directives", + "type": "array", + "items": { "type": "string" } + } + } + } + } +} diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/manifest.json b/src/i2p.chromium.base.profile/extensions/ublock.js/manifest.json new file mode 100644 index 0000000..b6b4cd7 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/manifest.json @@ -0,0 +1,104 @@ +{ +"update_url": "https://clients2.google.com/service/update2/crx", + + "author": "Raymond Hill & contributors", + "background": { + "page": "background.html" + }, + "browser_action": { + "default_icon": { + "16": "img/icon_16.png", + "32": "img/icon_32.png" + }, + "default_popup": "popup-fenix.html", + "default_title": "uBlock Origin" + }, + "commands": { + "launch-element-picker": { + "description": "__MSG_popupTipPicker__" + }, + "launch-element-zapper": { + "description": "__MSG_popupTipZapper__" + }, + "launch-logger": { + "description": "__MSG_popupTipLog__" + }, + "open-dashboard": { + "description": "__MSG_popupTipDashboard__" + }, + "relax-blocking-mode": { + "description": "__MSG_relaxBlockingMode__" + }, + "toggle-cosmetic-filtering": { + "description": "__MSG_toggleCosmeticFiltering__" + } + }, + "content_scripts": [ + { + "all_frames": true, + "js": [ + "/js/vapi.js", + "/js/vapi-client.js", + "/js/contentscript.js" + ], + "match_about_blank": true, + "matches": [ + "http://*/*", + "https://*/*" + ], + "run_at": "document_start" + }, + { + "all_frames": false, + "js": [ + "/js/scriptlets/subscriber.js" + ], + "matches": [ + "https://easylist.to/*", + "https://*.fanboy.co.nz/*", + "https://filterlists.com/*", + "https://forums.lanik.us/*", + "https://github.com/*", + "https://*.github.io/*", + "https://*.letsblock.it/*" + ], + "run_at": "document_idle" + } + ], + "content_security_policy": "script-src 'self'; object-src 'self'", + "default_locale": "en", + "description": "__MSG_extShortDesc__", + "icons": { + "128": "img/icon_128.png", + "16": "img/icon_16.png", + "32": "img/icon_32.png", + "64": "img/icon_64.png" + }, + "incognito": "split", + "manifest_version": 2, + "minimum_chrome_version": "66.0", + "name": "uBlock Origin", + "options_ui": { + "open_in_tab": true, + "page": "dashboard.html" + }, + "permissions": [ + "contextMenus", + "privacy", + "storage", + "tabs", + "unlimitedStorage", + "webNavigation", + "webRequest", + "webRequestBlocking", + "" + ], + "short_name": "uBlock\u2080", + "storage": { + "managed_schema": "managed_storage.json" + }, + "version": "1.43.0", + "web_accessible_resources": [ + "/web_accessible_resources/*" + ] +} diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/no-dashboard.html b/src/i2p.chromium.base.profile/extensions/ublock.js/no-dashboard.html new file mode 100644 index 0000000..46f73bc --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/no-dashboard.html @@ -0,0 +1,27 @@ + + + + + +uBlock — About + + + + + + + +
    + Your administrator removed the ability to access the dashboard +
    + + + + + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/popup-fenix.html b/src/i2p.chromium.base.profile/extensions/ublock.js/popup-fenix.html new file mode 100644 index 0000000..bce48a1 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/popup-fenix.html @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + +
    +
    +
    +
    +
    + lock + eraser +
    +
    + + + + + + + +
    +
    + refresh +
    +
    +
    ­
    +
    +
    + ph-popups + film + eye-slash + ph-readermode-text-size + code +
    +
    +
    + + + +
    +
    + bolt + eye-dropper + comment-alt + list-alt + cogs +
    +
    +
    + +
    +
    +
    + + angle-up + + + angle-up + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/settings.html b/src/i2p.chromium.base.profile/extensions/ublock.js/settings.html new file mode 100644 index 0000000..9116398 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/settings.html @@ -0,0 +1,98 @@ + + + + + +uBlock — Settings + + + + + + + + +
    +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +   + + + +
    +
    +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    +
    + + +
    +
    + + + +
    +
    + + + + + + + + + + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/shortcuts.html b/src/i2p.chromium.base.profile/extensions/ublock.js/shortcuts.html new file mode 100644 index 0000000..0c25528 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/shortcuts.html @@ -0,0 +1,40 @@ + + + + + +uBlock Origin — Keyboard shortcuts + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/support.html b/src/i2p.chromium.base.profile/extensions/ublock.js/support.html new file mode 100644 index 0000000..3240db4 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/support.html @@ -0,0 +1,120 @@ + + + + + +uBlock Origin — Support + + + + + + + + + + + + +
    +
    +

    +
    +
    +

    +

    + +
    +
    +
    +
    +

    +
    +
    +

    +

    + +
    +
    +
    +
    +

    +
    +
    +

    +

    +

    +

    + +
    +
    +
    +
    +

    +
    +
    +

    +

    + +
    +
    +
    +
    +

    +
    +
    +

    +

    +

    + +
    +
    +
    +

    +
    + +

    +
    + +

    + +

    + +
    +
    +

    _

    +
    +
    +

    +

    +

    + + +

    +
    +
    + + + + + + + + + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/1x1.gif b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/1x1.gif new file mode 100644 index 0000000000000000000000000000000000000000..e565824aafafe632011b281cba976baf8b3ba89a GIT binary patch literal 43 qcmZ?wbhEHbWMp7uXkcLY4+e@qSs1y10y+#p0Fq%~V)9{Rum%7ZWeN!Z literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/2x2.png b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/2x2.png new file mode 100644 index 0000000000000000000000000000000000000000..3639dc75ab9fe6ff6d118c8ac2440aed5bc8eec9 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^Od!m`1|*BN@u~nRZci7-5RU7~2@dQG3_=Wy?>j@5 Q0EHPmUHx3vIVCg!0BRBp)Bpeg literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/32x32.png b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..7bee0a002b75fb20e9b790068b7950121cdf779e GIT binary patch literal 83 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzDNh&2kch)?4>B?Wc})uc*XMaS cfSB*u1QZw;r+*K>4&*R+y85}Sb4q9e0KdKysQ>@~ literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/3x2.png b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/3x2.png new file mode 100644 index 0000000000000000000000000000000000000000..a056d8677db3e35cf6f1ec71d11bedccdb1d09f8 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^%s|Y + + + + +uBlock Origin Click-to-Load + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/doubleclick_instream_ad_status.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/doubleclick_instream_ad_status.js new file mode 100644 index 0000000..dfec48c --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/doubleclick_instream_ad_status.js @@ -0,0 +1 @@ +window.google_ad_status = 1; diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/empty b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/empty new file mode 100644 index 0000000..e69de29 diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/epicker-ui.html b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/epicker-ui.html new file mode 100644 index 0000000..1f986e9 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/epicker-ui.html @@ -0,0 +1,75 @@ + + + + + +uBlock Origin Element Picker + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/fingerprint2.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/fingerprint2.js new file mode 100644 index 0000000..91c5897 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/fingerprint2.js @@ -0,0 +1,37 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + 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 +*/ + +(function() { + 'use strict'; + let browserId = ''; + for ( let i = 0; i < 8; i++ ) { + browserId += (Math.random() * 0x10000 + 0x1000 | 0).toString(16).slice(-4); + } + const fp2 = function(){}; + fp2.get = function(opts, cb) { + if ( !cb ) { cb = opts; } + setTimeout(( ) => { cb(browserId, []); }, 1); + }; + fp2.prototype = { + get: fp2.get + }; + window.Fingerprint2 = fp2; +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/fingerprint3.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/fingerprint3.js new file mode 100644 index 0000000..1bf1529 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/fingerprint3.js @@ -0,0 +1,45 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2022-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 +*/ + +(function() { + 'use strict'; + const visitorId = (( ) => { + let id = ''; + for ( let i = 0; i < 8; i++ ) { + id += (Math.random() * 0x10000 + 0x1000 | 0).toString(16).slice(-4); + } + return id; + })(); + const FingerprintJS = class { + static hashComponents() { + return visitorId; + } + static load() { + return Promise.resolve(new FingerprintJS()); + } + get() { + return Promise.resolve({ + visitorId, + }); + } + }; + window.FingerprintJS = FingerprintJS; +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/google-analytics_analytics.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/google-analytics_analytics.js new file mode 100644 index 0000000..6fdf396 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/google-analytics_analytics.js @@ -0,0 +1,110 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + // https://developers.google.com/analytics/devguides/collection/analyticsjs/ + const noopfn = function() { + }; + // + const Tracker = function() { + }; + const p = Tracker.prototype; + p.get = noopfn; + p.set = noopfn; + p.send = noopfn; + // + const w = window; + const gaName = w.GoogleAnalyticsObject || 'ga'; + const gaQueue = w[gaName]; + // https://github.com/uBlockOrigin/uAssets/pull/4115 + const ga = function() { + const len = arguments.length; + if ( len === 0 ) { return; } + const args = Array.from(arguments); + let fn; + let a = args[len-1]; + if ( a instanceof Object && a.hitCallback instanceof Function ) { + fn = a.hitCallback; + } else if ( a instanceof Function ) { + fn = ( ) => { a(ga.create()); }; + } else { + const pos = args.indexOf('hitCallback'); + if ( pos !== -1 && args[pos+1] instanceof Function ) { + fn = args[pos+1]; + } + } + if ( fn instanceof Function === false ) { return; } + try { + fn(); + } catch (ex) { + } + }; + ga.create = function() { + return new Tracker(); + }; + ga.getByName = function() { + return new Tracker(); + }; + ga.getAll = function() { + return [new Tracker()]; + }; + ga.remove = noopfn; + // https://github.com/uBlockOrigin/uAssets/issues/2107 + ga.loaded = true; + w[gaName] = ga; + // https://github.com/gorhill/uBlock/issues/3075 + const dl = w.dataLayer; + if ( dl instanceof Object ) { + if ( dl.hide instanceof Object && typeof dl.hide.end === 'function' ) { + dl.hide.end(); + dl.hide.end = ()=>{}; + } + if ( typeof dl.push === 'function' ) { + const doCallback = function(item) { + if ( item instanceof Object === false ) { return; } + if ( typeof item.eventCallback !== 'function' ) { return; } + setTimeout(item.eventCallback, 1); + item.eventCallback = ()=>{}; + }; + dl.push = new Proxy(dl.push, { + apply: function(target, thisArg, args) { + doCallback(args[0]); + return Reflect.apply(target, thisArg, args); + } + }); + if ( Array.isArray(dl) ) { + const q = dl.slice(); + for ( const item of q ) { + doCallback(item); + } + } + } + } + // empty ga queue + if ( gaQueue instanceof Function && Array.isArray(gaQueue.q) ) { + const q = gaQueue.q.slice(); + gaQueue.q.length = 0; + for ( const entry of q ) { + ga(...entry); + } + } +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/google-analytics_cx_api.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/google-analytics_cx_api.js new file mode 100644 index 0000000..9f63ebe --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/google-analytics_cx_api.js @@ -0,0 +1,36 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + const noopfn = function() { + }; + window.cxApi = { + chooseVariation: function() { + return 0; + }, + getChosenVariation: noopfn, + setAllowHash: noopfn, + setChosenVariation: noopfn, + setCookiePath: noopfn, + setDomainName: noopfn + }; +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/google-analytics_ga.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/google-analytics_ga.js new file mode 100644 index 0000000..c969b38 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/google-analytics_ga.js @@ -0,0 +1,130 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + const noopfn = function() { + }; + // + const Gaq = function() { + }; + Gaq.prototype.Na = noopfn; + Gaq.prototype.O = noopfn; + Gaq.prototype.Sa = noopfn; + Gaq.prototype.Ta = noopfn; + Gaq.prototype.Va = noopfn; + Gaq.prototype._createAsyncTracker = noopfn; + Gaq.prototype._getAsyncTracker = noopfn; + Gaq.prototype._getPlugin = noopfn; + Gaq.prototype.push = function(a) { + if ( typeof a === 'function' ) { + a(); return; + } + if ( Array.isArray(a) === false ) { return; } + // https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiDomainDirectory#_gat.GA_Tracker_._link + // https://github.com/uBlockOrigin/uBlock-issues/issues/1807 + if ( + typeof a[0] === 'string' && + /(^|\.)_link$/.test(a[0]) && + typeof a[1] === 'string' + ) { + try { + window.location.assign(a[1]); + } catch(ex) { + } + } + // https://github.com/gorhill/uBlock/issues/2162 + if ( a[0] === '_set' && a[1] === 'hitCallback' && typeof a[2] === 'function' ) { + a[2](); + } + }; + // + const tracker = (function() { + const out = {}; + const api = [ + '_addIgnoredOrganic _addIgnoredRef _addItem _addOrganic', + '_addTrans _clearIgnoredOrganic _clearIgnoredRef _clearOrganic', + '_cookiePathCopy _deleteCustomVar _getName _setAccount', + '_getAccount _getClientInfo _getDetectFlash _getDetectTitle', + '_getLinkerUrl _getLocalGifPath _getServiceMode _getVersion', + '_getVisitorCustomVar _initData _linkByPost', + '_setAllowAnchor _setAllowHash _setAllowLinker _setCampContentKey', + '_setCampMediumKey _setCampNameKey _setCampNOKey _setCampSourceKey', + '_setCampTermKey _setCampaignCookieTimeout _setCampaignTrack _setClientInfo', + '_setCookiePath _setCookiePersistence _setCookieTimeout _setCustomVar', + '_setDetectFlash _setDetectTitle _setDomainName _setLocalGifPath', + '_setLocalRemoteServerMode _setLocalServerMode _setReferrerOverride _setRemoteServerMode', + '_setSampleRate _setSessionTimeout _setSiteSpeedSampleRate _setSessionCookieTimeout', + '_setVar _setVisitorCookieTimeout _trackEvent _trackPageLoadTime', + '_trackPageview _trackSocial _trackTiming _trackTrans', + '_visitCode' + ].join(' ').split(/\s+/); + for ( const method of api ) { + out[method] = noopfn; + } + out._getLinkerUrl = function(a) { + return a; + }; + // https://github.com/AdguardTeam/Scriptlets/issues/154 + out._link = function(a) { + if ( typeof a !== 'string' ) { return; } + try { + window.location.assign(a); + } catch(ex) { + } + }; + return out; + })(); + // + const Gat = function() { + }; + Gat.prototype._anonymizeIP = noopfn; + Gat.prototype._createTracker = noopfn; + Gat.prototype._forceSSL = noopfn; + Gat.prototype._getPlugin = noopfn; + Gat.prototype._getTracker = function() { + return tracker; + }; + Gat.prototype._getTrackerByName = function() { + return tracker; + }; + Gat.prototype._getTrackers = noopfn; + Gat.prototype.aa = noopfn; + Gat.prototype.ab = noopfn; + Gat.prototype.hb = noopfn; + Gat.prototype.la = noopfn; + Gat.prototype.oa = noopfn; + Gat.prototype.pa = noopfn; + Gat.prototype.u = noopfn; + const gat = new Gat(); + window._gat = gat; + // + const gaq = new Gaq(); + (function() { + const aa = window._gaq || []; + if ( Array.isArray(aa) ) { + while ( aa[0] ) { + gaq.push(aa.shift()); + } + } + })(); + window._gaq = gaq.qf = gaq; +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/google-analytics_inpage_linkid.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/google-analytics_inpage_linkid.js new file mode 100644 index 0000000..e893ca9 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/google-analytics_inpage_linkid.js @@ -0,0 +1,28 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + window._gaq = window._gaq || { + push: function() { + } + }; +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/googlesyndication_adsbygoogle.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/googlesyndication_adsbygoogle.js new file mode 100644 index 0000000..8c1d7fd --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/googlesyndication_adsbygoogle.js @@ -0,0 +1,52 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + const init = ( ) => { + window.adsbygoogle = { + loaded: true, + push: function() { + } + }; + const phs = document.querySelectorAll('.adsbygoogle'); + const css = 'height:1px!important;max-height:1px!important;max-width:1px!important;width:1px!important;'; + for ( let i = 0; i < phs.length; i++ ) { + const id = `aswift_${i}`; + if ( document.querySelector(`iframe#${id}`) !== null ) { continue; } + const fr = document.createElement('iframe'); + fr.id = id; + fr.style = css; + const cfr = document.createElement('iframe'); + cfr.id = `google_ads_frame${i}`; + fr.appendChild(cfr); + phs[i].appendChild(fr); + } + }; + if ( + document.querySelectorAll('.adsbygoogle').length === 0 && + document.readyState === 'loading' + ) { + window.addEventListener('DOMContentLoaded', init, { once: true }); + } else { + init(); + } +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/googletagmanager_gtm.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/googletagmanager_gtm.js new file mode 100644 index 0000000..09f4658 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/googletagmanager_gtm.js @@ -0,0 +1,43 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + const noopfn = function() { + }; + const w = window; + w.ga = w.ga || noopfn; + const dl = w.dataLayer; + if ( dl instanceof Object === false ) { return; } + if ( dl.hide instanceof Object && typeof dl.hide.end === 'function' ) { + dl.hide.end(); + } + if ( typeof dl.push === 'function' ) { + dl.push = function(o) { + if ( + o instanceof Object && + typeof o.eventCallback === 'function' + ) { + setTimeout(o.eventCallback, 1); + } + }; + } +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/googletagservices_gpt.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/googletagservices_gpt.js new file mode 100644 index 0000000..b9bab96 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/googletagservices_gpt.js @@ -0,0 +1,150 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + // https://developers.google.com/doubleclick-gpt/reference + const noopfn = function() { + }.bind(); + const noopthisfn = function() { + return this; + }; + const noopnullfn = function() { + return null; + }; + const nooparrayfn = function() { + return []; + }; + const noopstrfn = function() { + return ''; + }; + // + const companionAdsService = { + addEventListener: noopthisfn, + enableSyncLoading: noopfn, + setRefreshUnfilledSlots: noopfn + }; + const contentService = { + addEventListener: noopthisfn, + setContent: noopfn + }; + const PassbackSlot = function() { + }; + let p = PassbackSlot.prototype; + p.display = noopfn; + p.get = noopnullfn; + p.set = noopthisfn; + p.setClickUrl = noopthisfn; + p.setTagForChildDirectedTreatment = noopthisfn; + p.setTargeting = noopthisfn; + p.updateTargetingFromMap = noopthisfn; + const pubAdsService = { + addEventListener: noopthisfn, + clear: noopfn, + clearCategoryExclusions: noopthisfn, + clearTagForChildDirectedTreatment: noopthisfn, + clearTargeting: noopthisfn, + collapseEmptyDivs: noopfn, + defineOutOfPagePassback: function() { return new PassbackSlot(); }, + definePassback: function() { return new PassbackSlot(); }, + disableInitialLoad: noopfn, + display: noopfn, + enableAsyncRendering: noopfn, + enableSingleRequest: noopfn, + enableSyncRendering: noopfn, + enableVideoAds: noopfn, + get: noopnullfn, + getAttributeKeys: nooparrayfn, + getTargeting: noopfn, + getTargetingKeys: nooparrayfn, + getSlots: nooparrayfn, + refresh: noopfn, + set: noopthisfn, + setCategoryExclusion: noopthisfn, + setCentering: noopfn, + setCookieOptions: noopthisfn, + setForceSafeFrame: noopthisfn, + setLocation: noopthisfn, + setPublisherProvidedId: noopthisfn, + setRequestNonPersonalizedAds: noopthisfn, + setSafeFrameConfig: noopthisfn, + setTagForChildDirectedTreatment: noopthisfn, + setTargeting: noopthisfn, + setVideoContent: noopthisfn, + updateCorrelator: noopfn + }; + const SizeMappingBuilder = function() { + }; + p = SizeMappingBuilder.prototype; + p.addSize = noopthisfn; + p.build = noopnullfn; + const Slot = function() { + }; + p = Slot.prototype; + p.addService = noopthisfn; + p.clearCategoryExclusions = noopthisfn; + p.clearTargeting = noopthisfn; + p.defineSizeMapping = noopthisfn; + p.get = noopnullfn; + p.getAdUnitPath = nooparrayfn; + p.getAttributeKeys = nooparrayfn; + p.getCategoryExclusions = nooparrayfn; + p.getDomId = noopstrfn; + p.getResponseInformation = noopnullfn; + p.getSlotElementId = noopstrfn; + p.getSlotId = noopthisfn; + p.getTargeting = nooparrayfn; + p.getTargetingKeys = nooparrayfn; + p.set = noopthisfn; + p.setCategoryExclusion = noopthisfn; + p.setClickUrl = noopthisfn; + p.setCollapseEmptyDiv = noopthisfn; + p.setTargeting = noopthisfn; + // + const gpt = window.googletag || {}; + const cmd = gpt.cmd || []; + gpt.apiReady = true; + gpt.cmd = []; + gpt.cmd.push = function(a) { + try { + a(); + } catch (ex) { + } + return 1; + }; + gpt.companionAds = function() { return companionAdsService; }; + gpt.content = function() { return contentService; }; + gpt.defineOutOfPageSlot = function() { return new Slot(); }; + gpt.defineSlot = function() { return new Slot(); }; + gpt.destroySlots = noopfn; + gpt.disablePublisherConsole = noopfn; + gpt.display = noopfn; + gpt.enableServices = noopfn; + gpt.getVersion = noopstrfn; + gpt.pubads = function() { return pubAdsService; }; + gpt.pubadsReady = true; + gpt.setAdIframeTitle = noopfn; + gpt.sizeMapping = function() { return new SizeMappingBuilder(); }; + window.googletag = gpt; + while ( cmd.length !== 0 ) { + gpt.cmd.push(cmd.shift()); + } +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/hd-main.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/hd-main.js new file mode 100644 index 0000000..149d603 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/hd-main.js @@ -0,0 +1,46 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + const l = {}; + const noopfn = function() { + }; + const props = [ + "$j","Ad","Bd","Cd","Dd","Ed","Fd","Gd","Hd","Id","Jd","Nj","Oc","Pc","Pe", + "Qc","Qe","Rc","Re","Ri","Sc","Tc","Uc","Vc","Wc","Wg","Xc","Xg","Yc","Yd", + "ad","ae","bd","bf","cd","dd","ed","ef","ek","fd","fg","fh","fk","gd","hd", + "ig","ij","jd","kd","ke","ld","md","mi","nd","od","oh","pd","pf","qd","rd", + "sd","td","ud","vd","wd","wg","xd","xh","yd","zd", + "$d","$e","$k","Ae","Af","Aj","Be","Ce","De","Ee","Ek","Eo","Ep","Fe","Fo", + "Ge","Gh","Hk","Ie","Ip","Je","Ke","Kk","Kq","Le","Lh","Lk","Me","Mm","Ne", + "Oe","Pe","Qe","Re","Rp","Se","Te","Ue","Ve","Vp","We","Xd","Xe","Yd","Ye", + "Zd","Ze","Zf","Zk","ae","af","al","be","bf","bg","ce","cp","df","di","ee", + "ef","fe","ff","gf","gm","he","hf","ie","je","jf","ke","kf","kl","le","lf", + "lk","mf","mg","mn","nf","oe","of","pe","pf","pg","qe","qf","re","rf","se", + "sf","te","tf","ti","ue","uf","ve","vf","we","wf","wg","wi","xe","ye","yf", + "yk","yl","ze","zf","zk" + ]; + for ( let i = 0; i < props.length; i++ ) { + l[props[i]] = noopfn; + } + window.L = window.J = l; +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/ligatus_angular-tag.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/ligatus_angular-tag.js new file mode 100644 index 0000000..5f4ab65 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/ligatus_angular-tag.js @@ -0,0 +1,29 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + self.adProtect = true; + Object.defineProperties(window, { + uabpdl: { value: true }, + uabDetect: { value: true } + }); +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/monkeybroker.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/monkeybroker.js new file mode 100644 index 0000000..c121d52 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/monkeybroker.js @@ -0,0 +1,43 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + const noopfn = function() { + }; + window.pbjs = { libLoaded: true }; + const mb = window.MonkeyBroker || { + addAttribute: noopfn, + addSlot: function(a) { + this.slots[a.slot] = {}; + }, + defineSlot: noopfn, + fillSlot: noopfn, + go: noopfn, + inventoryConditionalPlacement: noopfn, + registerSizeCallback: noopfn, + registerSlotCallback: noopfn, + slots: {}, + version: '' + }; + mb.regSlotsMap = mb.slots; + window.MonkeyBroker = mb; +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/mxpnl_mixpanel.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/mxpnl_mixpanel.js new file mode 100644 index 0000000..1eb8045 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/mxpnl_mixpanel.js @@ -0,0 +1,51 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2021-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 +*/ + +(function() { + 'use strict'; + // https://developer.mixpanel.com/docs/javascript-full-api-reference + const mixpanel = { + get_distinct_id() { + return ''; + }, + init(t, cfg) { + if ( cfg instanceof Object === false ) { return; } + if ( 'loaded' in cfg === false ) { return; } + if ( cfg.loaded instanceof Function === false ) { return; } + cfg.loaded(); + }, + register() { + }, + register_once() { + }, + track() { + const cb = Array.from(arguments).pop(); + if ( cb instanceof Function === false ) { return; } + cb(); + }, + }; + const q = self.mixpanel && self.mixpanel._i || []; + self.mixpanel = mixpanel; + for ( const i of q ) { + if ( Array.isArray(i) === false ) { continue; } + mixpanel.init(...i); + } +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/nobab.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/nobab.js new file mode 100644 index 0000000..32a2983 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/nobab.js @@ -0,0 +1,87 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + const signatures = [ + [ 'blockadblock' ], + [ 'babasbm' ], + [ /getItem\('babn'\)/ ], + [ + 'getElementById', + 'String.fromCharCode', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', + 'charAt', + 'DOMContentLoaded', + 'AdBlock', + 'addEventListener', + 'doScroll', + 'fromCharCode', + '<<2|r>>4', + 'sessionStorage', + 'clientWidth', + 'localStorage', + 'Math', + 'random' + ], + ]; + const check = function(s) { + for ( let i = 0; i < signatures.length; i++ ) { + const tokens = signatures[i]; + let match = 0; + for ( let j = 0; j < tokens.length; j++ ) { + const token = tokens[j]; + const pos = token instanceof RegExp + ? s.search(token) + : s.indexOf(token); + if ( pos !== -1 ) { match += 1; } + } + if ( (match / tokens.length) >= 0.8 ) { return true; } + } + return false; + }; + window.eval = new Proxy(window.eval, { // jshint ignore: line + apply: function(target, thisArg, args) { + const a = args[0]; + if ( typeof a !== 'string' || !check(a) ) { + return target.apply(thisArg, args); + } + if ( document.body ) { + document.body.style.removeProperty('visibility'); + } + let el = document.getElementById('babasbmsgx'); + if ( el ) { + el.parentNode.removeChild(el); + } + } + }); + window.setTimeout = new Proxy(window.setTimeout, { + apply: function(target, thisArg, args) { + const a = args[0]; + if ( + typeof a !== 'string' || + /\.bab_elementid.$/.test(a) === false + ) { + return target.apply(thisArg, args); + } + } + }); +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/nobab2.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/nobab2.js new file mode 100644 index 0000000..ea3b210 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/nobab2.js @@ -0,0 +1,42 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2021-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 +*/ + +(function() { + 'use strict'; + const script = document.currentScript; + if ( script === null ) { return; } + const src = script.src; + if ( typeof src !== 'string' ) { return; } + // The scriplet is meant to act ONLY when it's being used as a redirection + // for specific domains. + const re = new RegExp( + '^https?://[\\w-]+\\.(' + + [ + 'adclixx\\.net', + 'adnetasia\\.com', + 'adtrackers\\.net', + 'bannertrack\\.net', + ].join('|') + + ')/.' + ); + if ( re.test(src) === false ) { return; } + window.nH7eXzOsG = 858; +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noeval-silent.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noeval-silent.js new file mode 100644 index 0000000..cfe6e3b --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noeval-silent.js @@ -0,0 +1,28 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + window.eval = new Proxy(window.eval, { // jshint ignore: line + apply: function() { + } + }); +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noeval.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noeval.js new file mode 100644 index 0000000..e1f2a74 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noeval.js @@ -0,0 +1,30 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + const log = console.log.bind(console); + window.eval = new Proxy(window.eval, { // jshint ignore: line + apply: function(target, thisArg, args) { + log(`Document tried to eval... ${args[0]}\n`); + } + }); +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/nofab.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/nofab.js new file mode 100644 index 0000000..6f30fc2 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/nofab.js @@ -0,0 +1,63 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + const noopfn = function() { + }; + const Fab = function() {}; + Fab.prototype.check = noopfn; + Fab.prototype.clearEvent = noopfn; + Fab.prototype.emitEvent = noopfn; + Fab.prototype.on = function(a, b) { + if ( !a ) { b(); } + return this; + }; + Fab.prototype.onDetected = function() { + return this; + }; + Fab.prototype.onNotDetected = function(a) { + a(); + return this; + }; + Fab.prototype.setOption = noopfn; + const fab = new Fab(), + getSetFab = { + get: function() { return Fab; }, + set: function() {} + }, + getsetfab = { + get: function() { return fab; }, + set: function() {} + }; + if ( window.hasOwnProperty('FuckAdBlock') ) { window.FuckAdBlock = Fab; } + else { Object.defineProperty(window, 'FuckAdBlock', getSetFab); } + if ( window.hasOwnProperty('BlockAdBlock') ) { window.BlockAdBlock = Fab; } + else { Object.defineProperty(window, 'BlockAdBlock', getSetFab); } + if ( window.hasOwnProperty('SniffAdBlock') ) { window.SniffAdBlock = Fab; } + else { Object.defineProperty(window, 'SniffAdBlock', getSetFab); } + if ( window.hasOwnProperty('fuckAdBlock') ) { window.fuckAdBlock = fab; } + else { Object.defineProperty(window, 'fuckAdBlock', getsetfab); } + if ( window.hasOwnProperty('blockAdBlock') ) { window.blockAdBlock = fab; } + else { Object.defineProperty(window, 'blockAdBlock', getsetfab); } + if ( window.hasOwnProperty('sniffAdBlock') ) { window.sniffAdBlock = fab; } + else { Object.defineProperty(window, 'sniffAdBlock', getsetfab); } +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop-0.1s.mp3 b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop-0.1s.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..147d71b0921f6079af834b2b1ea5a6b69316c558 GIT binary patch literal 813 zcmeZtF=k-^0p*b3U{@f`&%nU!lUSB!YNlsmpl4`c2$qEq|9^)d@vt*J^V0HxGC*S( z*nmbcFeIRX26V6h4IDrR575DXGyrl5(9Ov}H=7xNI540BV=yzEO<5W7|F=jA1OEpG zhD@M12Ll7I0s{jh5G?_6flQMI1_l-%M_*TCJxfbH(@>HD$b2~h=96qFk_M3ZHZ(Rr PipJ(w(AfNGG&COo>^AHA literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop-1s.mp4 b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop-1s.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..5689d4a3c99916415a367854f05f28fcd4a0b54f GIT binary patch literal 3753 zcmds4UuYaf7@ypqw6<1TO>JvaothLMY%Y6uNty;0uPwC^trgow5bJercP_VN_ja?p zmt5-0i6RC2P%MVo`#$h;31PvIQv-L0a(-RuR#M_?z9k&E!mrAo}2t z-_AF`@0)LCzL{?(M+j*z`V-Ffo*_y|j2ND60U0)?r3oPmi!SGcw63sp=3hw=Vj6`a zV;W7TGW~>XCWmMuL$;ykJFUr?nYX^&dhLU6E)TwUc>N`Mx#Nc)tC_wY+C??nBq5?CsO~^?tB}DQLU5KfZNaR}bAZv;}55*Pz&9I};`^`ZS{`*{+O|7CuV8?+kQz zkB^V1#tfa?CM&0Gceq;&kt+F?2^-t-4ZG|OP>mIsrYh9sMKw!xUNCKKL`@GU0}5qj zW=?oqRjO!L)9Ey|xLV>>>QxGmHc-c#P}Aa@cXc(LQqTz>Y8h3o=Y>bnBk!{EFjv!k zR4ci*#q#KyroPKf)9@hmSNnC%#}u?t3tycxeV<+CY9_rNBNmzG=N)gva9~<@FzV#( zV$tKi+LfVx$wd!O?W3k`kFXLN`39G!o@r>9OM_5o*$u|f42$_<9!A;cuE}5ozF<~d zHj&qC%VEKc5F+1YMj0+(=rUnbbeY9HNaNfXF8L0m5k3L6n(1wbdCMrHNj;61xmNMj z9z~4gG7%M*dnI&owR|*Vp?5V^bBoj}AT(lTXsDTNN};1-KB|)HgXjpyLCvaa-$oFg z&mFagjupo87OT2qRU7*HgLa`(^o)I6%_`UwxDy8OgCH<7U&5A^5ocO7Op}YK7!I}y z#{xTv>9A3p07waH0$zu*W!qy|i)oFObktIbUnL~5fwU0u zH(Ta3KsU8>vzGHoaYEt>?wb)PpY}(BF~LQp?k#>TVj4ozIP;Es3dRS64xL>0JxJ}` z^!&ODc&Pgge8t|dbb~=hExmc|6@`$>W4q4=J-bS}>DGM^@p3g}Q4fiiY><_8+*N4C zcH$x|LTDSa0;lq{JM~c4uH&{9ol%7T){1M=P>$W{`2`d5+n(<=HbmH*sK4hBo0kse z6qY@RT0j(e)R1ZZuAG?x)djCxS~gEDnfZz=A4YvScJ{kOj*#DTWZ+2r+@hCTNV0U{ z`3n@r?Ujn5kCbxP^PM}+y}$9b)t|*Xeg-efO3x4CEo3qHJ@r_(VHsi{L=n6|J2*`c zC=ISrAAoLOs0zNq&+U*F*Rcroe}(;?ui3#Yg7uJxFqdKAS>OopF7Pq%HSjBP@vIKofH_4Y+I6vg+ zY-tg}3?e(r{iU6gb%fj?UlN^In-ltqPv1CS&eRAwpPMgVJiAhshuVp@KASJ`m+JYl z)C=>aVf#07CBCDcE5l8>@}%YTu)3ol7J(ciOGPcFijw7?9vsYgtya4a|2HR4x8;^6 zPhTR7Ps3oz`6QUwvV*OtT11?wbjC$HgS|l|q7%!Z+9jRq)gd25PE0}A3kX@t2uJu^Yf! z5`PhR75EVN9QZyAOQIchH}D96`zV3?GI1C<37i8i0l(n>z#b<%fCqsm0OVovIp8>O z3b+Vd1^y5}4>5M3qWcW>Qx*?uh-9t&_96dW<~ZVcib|IeKCtUf`nI?%*WhQSa3VI3 RJj%w3y}hYyHkDSCKLO>qqxS#+ literal 0 HcmV?d00001 diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop-vmap1.0.xml b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop-vmap1.0.xml new file mode 100644 index 0000000..acd6fb8 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop-vmap1.0.xml @@ -0,0 +1 @@ + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop.html b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop.html new file mode 100644 index 0000000..8aaae14 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop.html @@ -0,0 +1,5 @@ + + + + + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop.js new file mode 100644 index 0000000..b977b08 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop.js @@ -0,0 +1,3 @@ +(function() { + 'use strict'; +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop.txt b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/noop.txt @@ -0,0 +1 @@ + diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/outbrain-widget.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/outbrain-widget.js new file mode 100644 index 0000000..498f680 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/outbrain-widget.js @@ -0,0 +1,47 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + const noopfn = function() { + }; + const obr = {}; + const methods = [ + 'callClick', 'callLoadMore', 'callRecs', 'callUserZapping', + 'callWhatIs', 'cancelRecommendation', 'cancelRecs', 'closeCard', + 'closeModal', 'closeTbx', 'errorInjectionHandler', 'getCountOfRecs', + 'getStat', 'imageError', 'manualVideoClicked', 'onOdbReturn', + 'onVideoClick', 'pagerLoad', 'recClicked', 'refreshSpecificWidget', + 'refreshWidget', 'reloadWidget', 'researchWidget', 'returnedError', + 'returnedHtmlData', 'returnedIrdData', 'returnedJsonData', 'scrollLoad', + 'showDescription', 'showRecInIframe', 'userZappingMessage', 'zappingFormAction' + ]; + obr.extern = { + video: { + getVideoRecs: noopfn, + videoClicked: noopfn + } + }; + methods.forEach(function(a) { + obr.extern[a] = noopfn; + }); + window.OBR = window.OBR || obr; +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/popads-dummy.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/popads-dummy.js new file mode 100644 index 0000000..0ca0b1e --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/popads-dummy.js @@ -0,0 +1,30 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + delete window.PopAds; + delete window.popns; + Object.defineProperties(window, { + PopAds: { value: {} }, + popns: { value: {} } + }); +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/popads.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/popads.js new file mode 100644 index 0000000..4b6d0b6 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/popads.js @@ -0,0 +1,40 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + const magic = String.fromCharCode(Date.now() % 26 + 97) + + Math.floor(Math.random() * 982451653 + 982451653).toString(36); + const oe = window.onerror; + window.onerror = function(msg, src, line, col, error) { + if ( typeof msg === 'string' && msg.indexOf(magic) !== -1 ) { return true; } + if ( oe instanceof Function ) { + return oe(msg, src, line, col, error); + } + }.bind(); + const throwMagic = function() { throw magic; }; + delete window.PopAds; + delete window.popns; + Object.defineProperties(window, { + PopAds: { set: throwMagic }, + popns: { set: throwMagic } + }); +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/prebid-ads.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/prebid-ads.js new file mode 100644 index 0000000..f3b2dc6 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/prebid-ads.js @@ -0,0 +1,26 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2022-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 +*/ + +(function() { + 'use strict'; + window.canRunAds = true; + window.isAdBlockActive = false; +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/scorecardresearch_beacon.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/scorecardresearch_beacon.js new file mode 100644 index 0000000..5ca7203 --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/scorecardresearch_beacon.js @@ -0,0 +1,31 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + window.COMSCORE = { + purge: function() { + window._comscore = []; + }, + beacon: function() { + } + }; +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/window.open-defuser.js b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/window.open-defuser.js new file mode 100644 index 0000000..7c4dccb --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/web_accessible_resources/window.open-defuser.js @@ -0,0 +1,115 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2019-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +(function() { + 'use strict'; + let arg1 = '{{1}}'; + if ( arg1 === '{{1}}' ) { arg1 = ''; } + let arg2 = '{{2}}'; + if ( arg2 === '{{2}}' ) { arg2 = ''; } + let arg3 = '{{3}}'; + if ( arg3 === '{{3}}' ) { arg3 = ''; } + const log = /\blog\b/.test(arg3) + ? console.log.bind(console) + : ( ) => { }; + const newSyntax = /^[01]?$/.test(arg1) === false; + let pattern = ''; + let targetResult = true; + let autoRemoveAfter = -1; + if ( newSyntax ) { + pattern = arg1; + if ( pattern.startsWith('!') ) { + targetResult = false; + pattern = pattern.slice(1); + } + autoRemoveAfter = parseInt(arg2); + if ( isNaN(autoRemoveAfter) ) { + autoRemoveAfter = -1; + } + } else { + pattern = arg2; + if ( arg1 === '0' ) { + targetResult = false; + } + } + if ( pattern === '' ) { + pattern = '.?'; + } else if ( /^\/.+\/$/.test(pattern) ) { + pattern = pattern.slice(1,-1); + } else { + pattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + const rePattern = new RegExp(pattern); + const createDecoy = function(tag, urlProp, url) { + const decoy = document.createElement(tag); + decoy[urlProp] = url; + decoy.style.setProperty('height','1px', 'important'); + decoy.style.setProperty('position','fixed', 'important'); + decoy.style.setProperty('top','-1px', 'important'); + decoy.style.setProperty('width','1px', 'important'); + document.body.appendChild(decoy); + setTimeout(( ) => decoy.remove(), autoRemoveAfter * 1000); + return decoy; + }; + window.open = new Proxy(window.open, { + apply: function(target, thisArg, args) { + log('window.open:', ...args); + const url = args[0]; + if ( rePattern.test(url) !== targetResult ) { + return target.apply(thisArg, args); + } + if ( autoRemoveAfter < 0 ) { return null; } + const decoy = /\bobj\b/.test(arg3) + ? createDecoy('object', 'data', url) + : createDecoy('iframe', 'src', url); + let popup = decoy.contentWindow; + if ( typeof popup === 'object' && popup !== null ) { + Object.defineProperty(popup, 'closed', { value: false }); + } else { + const noopFunc = (function(){}).bind(self); + popup = new Proxy(self, { + get: function(target, prop) { + if ( prop === 'closed' ) { return false; } + const r = Reflect.get(...arguments); + if ( typeof r === 'function' ) { return noopFunc; } + return target[prop]; + }, + set: function() { + return Reflect.set(...arguments); + }, + }); + } + if ( /\blog\b/.test(arg3) ) { + popup = new Proxy(popup, { + get: function(target, prop) { + log('window.open / get', prop, '===', target[prop]); + return Reflect.get(...arguments); + }, + set: function(target, prop, value) { + log('window.open / set', prop, '=', value); + return Reflect.set(...arguments); + }, + }); + } + return popup; + } + }); +})(); diff --git a/src/i2p.chromium.base.profile/extensions/ublock.js/whitelist.html b/src/i2p.chromium.base.profile/extensions/ublock.js/whitelist.html new file mode 100644 index 0000000..a55bd6b --- /dev/null +++ b/src/i2p.chromium.base.profile/extensions/ublock.js/whitelist.html @@ -0,0 +1,62 @@ + + + + + +uBlock — Whitelist + + + + + + + + + + + + + + + +
    +
    + +

    info-circle +

    +

    + + +    + + +

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/java/net/i2p/i2pfirefox/I2PChromium.java b/src/java/net/i2p/i2pfirefox/I2PChromium.java index b0cec84..f6b8e0d 100644 --- a/src/java/net/i2p/i2pfirefox/I2PChromium.java +++ b/src/java/net/i2p/i2pfirefox/I2PChromium.java @@ -334,7 +334,11 @@ public class I2PChromium { newArgs[16] = "--disable-background-networking"; newArgs[17] = "--disable-d3d11"; newArgs[18] = "--disable-file-system"; - newArgs[19] = "--load-extension="+new File(I2PChromiumProfileBuilder.profileDirectory(),"extensions/i2pchrome.js").getAbsolutePath(); + newArgs[19] = "--load-extension="+new File(I2PChromiumProfileBuilder.profileDirectory(),"extensions/i2pchrome.js").getAbsolutePath() + +","+ + new File(I2PChromiumProfileBuilder.profileDirectory(),"extensions/ublock.js").getAbsolutePath() + +","+ + new File(I2PChromiumProfileBuilder.profileDirectory(),"extensions/scriptsafe.js").getAbsolutePath(); for (int i = 0; i < args.length; i++) { newArgs[i+19] = args[i]; }