From 33749d2d3f88a75cf04093771cd61c0e2b72d2ab Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 17 Feb 2024 12:53:57 -0500 Subject: [PATCH 001/180] Use requestIdleCallback() in `href-sanitizer` scriptlet Instead of requestAnimationFrame(). --- assets/resources/scriptlets.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/resources/scriptlets.js b/assets/resources/scriptlets.js index e135a53c708c5..2ffdf6172636b 100644 --- a/assets/resources/scriptlets.js +++ b/assets/resources/scriptlets.js @@ -3455,7 +3455,7 @@ function hrefSanitizer( if ( shouldSanitize ) { break; } } if ( shouldSanitize === false ) { return; } - timer = self.requestAnimationFrame(( ) => { + timer = self.requestIdleCallback(( ) => { timer = undefined; sanitize(); }); From be3e36601941ed69c911bf327333f2f5bed4c3ec Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 17 Feb 2024 19:57:44 -0500 Subject: [PATCH 002/180] Escape special whitespace characters in attribute values Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/3127 Reference: https://mathiasbynens.be/notes/css-escapes --- src/js/static-filtering-parser.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index cc955c5f604d5..aa118c9dec198 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -3458,6 +3458,8 @@ class ExtSelectorCompiler { // https://github.com/uBlockOrigin/uBlock-issues/issues/2300 // Unquoted attribute values are parsed as Identifier instead of String. + // https://github.com/uBlockOrigin/uBlock-issues/issues/3127 + // Escape [\t\n\v\f\r] astSerializePart(part) { const out = []; const { data } = part; @@ -3473,7 +3475,14 @@ class ExtSelectorCompiler { if ( typeof value !== 'string' ) { value = data.value.name; } - value = value.replace(/["\\]/g, '\\$&'); + if ( /["\\]/.test(value) ) { + value = value.replace(/["\\]/g, '\\$&'); + } + if ( /[\x09-\x0D]/.test(value) ) { + value = value.replace(/[\x09-\x0D]/g, s => + `\\${s.charCodeAt(0).toString(16).toUpperCase()} ` + ); + } let flags = ''; if ( typeof data.flags === 'string' ) { if ( /^(is?|si?)$/.test(data.flags) === false ) { return; } From e6e01d96a460833e960dce54801b9c803f2a4145 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 17 Feb 2024 19:59:53 -0500 Subject: [PATCH 003/180] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 3ebf789f5a8df..c1418601688bf 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.56.0 +1.56.1.0 From a7e8485b327898816587bf621ff42ba794e34c1d Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 17 Feb 2024 20:01:42 -0500 Subject: [PATCH 004/180] Update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4ce13c82740d..bcfc51d56cdb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ ## Fixes / changes +- [Escape special whitespace characters in attribute values](https://github.com/gorhill/uBlock/commit/be3e366019) + +---------- + +# 1.56.0 + +## Fixes / changes + - [Mind that multiple `uritransform` may apply to a single request](https://github.com/gorhill/uBlock/commit/2a5a444482) - [Fix incorrect built-in filtering expression in logger](https://github.com/gorhill/uBlock/commit/9bff0c2f94) - [Fix improper invalidation of valid `uritransform` exception filters](https://github.com/gorhill/uBlock/commit/21ec5a277c) From 0096b74d461821a6ad2d46c109aa2def049f7e76 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 17 Feb 2024 20:06:02 -0500 Subject: [PATCH 005/180] Make Firefox dev build auto-update --- dist/firefox/updates.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index cd3068af48ad3..ad32bd64e13e0 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,9 +3,9 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.55.1.105", + "version": "1.56.1.0", "browser_specific_settings": { "gecko": { "strict_min_version": "78.0" } }, - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.55.1rc5/uBlock0_1.55.1rc5.firefox.signed.xpi" + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b0/uBlock0_1.56.1b0.firefox.signed.xpi" } ] } From 9666eeb9cffd5de86fd795745450888d880c8eb0 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 17 Feb 2024 20:25:41 -0500 Subject: [PATCH 006/180] Do not treat selectors as "common" when char 0x09-0x0D are in attr value --- src/js/static-filtering-parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index aa118c9dec198..34828bc7af71b 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -3118,7 +3118,7 @@ class ExtSelectorCompiler { // context. const cssIdentifier = '[A-Za-z_][\\w-]*'; const cssClassOrId = `[.#]${cssIdentifier}`; - const cssAttribute = `\\[${cssIdentifier}(?:[*^$]?="[^"\\]\\\\]+")?\\]`; + const cssAttribute = `\\[${cssIdentifier}(?:[*^$]?="[^"\\]\\\\\\x09-\\x0D]+")?\\]`; const cssSimple = '(?:' + `${cssIdentifier}(?:${cssClassOrId})*(?:${cssAttribute})*` + '|' + From fef26e234dfdff2bcb791b77601d65570ceb6358 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 18 Feb 2024 08:00:34 -0500 Subject: [PATCH 007/180] Make the "untrusted sources" warning always visible It won't disappear when vertical space is restricted. --- src/1p-filters.html | 2 +- src/css/1p-filters.css | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/1p-filters.html b/src/1p-filters.html index bafa9922c557f..965972598b6f0 100644 --- a/src/1p-filters.html +++ b/src/1p-filters.html @@ -23,7 +23,7 @@
-

question-circle

+

question-circle

diff --git a/src/css/1p-filters.css b/src/css/1p-filters.css index 83e5ee00b1d28..f6498c493231a 100644 --- a/src/css/1p-filters.css +++ b/src/css/1p-filters.css @@ -18,7 +18,7 @@ body { :root.mobile body { min-height: unset; } -[data-i18n="1pTrustWarning"] { +html:not(.mobile) [data-i18n="1pTrustWarning"] { font-weight: bold; } .codeMirrorContainer { From e527a8f9af7f9ca8f25f0cff19646d77d85a4edb Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 19 Feb 2024 10:59:12 -0500 Subject: [PATCH 008/180] Support logging details of calls to `json-prune-fetch-response` Example: example.com##+js(json-prune-fetch-response) This will output to the logger details of all fetch() with a Response.json() call. Related discussion: https://github.com/uBlockOrigin/uAssets/discussions/22556 --- assets/resources/scriptlets.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/assets/resources/scriptlets.js b/assets/resources/scriptlets.js index 2ffdf6172636b..9b079e68d6ff7 100644 --- a/assets/resources/scriptlets.js +++ b/assets/resources/scriptlets.js @@ -1198,10 +1198,10 @@ function jsonPruneFetchResponseFn( const extraArgs = safe.getExtraArgs(Array.from(arguments), 2); const propNeedles = parsePropertiesToMatch(extraArgs.propsToMatch, 'url'); const stackNeedle = safe.initPattern(extraArgs.stackToMatch || '', { canNegate: true }); + const logall = rawPrunePaths === ''; const applyHandler = function(target, thisArg, args) { const fetchPromise = Reflect.apply(target, thisArg, args); - if ( rawPrunePaths === '' ) { return fetchPromise; } - let outcome = 'match'; + let outcome = logall ? 'nomatch' : 'match'; if ( propNeedles.size !== 0 ) { const objs = [ args[0] instanceof Object ? args[0] : { url: args[0] } ]; if ( objs[0] instanceof Request ) { @@ -1218,14 +1218,18 @@ function jsonPruneFetchResponseFn( outcome = 'nomatch'; } } - if ( outcome === 'nomatch' ) { return fetchPromise; } - if ( safe.logLevel > 1 ) { + if ( logall === false && outcome === 'nomatch' ) { return fetchPromise; } + if ( safe.logLevel > 1 && outcome !== 'nomatch' && propNeedles.size !== 0 ) { safe.uboLog(logPrefix, `Matched optional "propsToMatch"\n${extraArgs.propsToMatch}`); } return fetchPromise.then(responseBefore => { const response = responseBefore.clone(); return response.json().then(objBefore => { if ( typeof objBefore !== 'object' ) { return responseBefore; } + if ( logall ) { + safe.uboLog(logPrefix, safe.JSON_stringify(objBefore, null, 2)); + return responseBefore; + } const objAfter = objectPruneFn( objBefore, rawPrunePaths, From 439a059cca5642da44ee620c0a21a57b194d109a Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 19 Feb 2024 11:06:39 -0500 Subject: [PATCH 009/180] Remove obsolete trusted directives All supported browsers now have the concept of priviledged pages, there is no need for these extra trusted-site directives. Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/3101 --- src/js/background.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/js/background.js b/src/js/background.js index 51f3212debc7a..470a13177f827 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -168,14 +168,8 @@ const µBlock = { // jshint ignore:line 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: { From c4d2dcd8358fffd9b27aa96aa674d5e43bada0a1 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 25 Feb 2024 18:27:07 -0500 Subject: [PATCH 010/180] Add ability to clean dist/build/[assets-related folders] --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9c03018d5765b..194a3af43af08 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # https://stackoverflow.com/a/6273809 run_options := $(filter-out $@,$(MAKECMDGOALS)) -.PHONY: all clean test lint chromium opera firefox npm dig mv3 mv3-quick \ +.PHONY: all clean cleanassets test lint chromium opera firefox npm dig mv3 mv3-quick \ compare maxcost medcost mincost modifiers record wasm sources := $(wildcard assets/* assets/*/* dist/version src/* src/*/* src/*/*/* src/*/*/*/*) @@ -73,6 +73,8 @@ dist/build/uAssets: clean: rm -rf dist/build tmp/node_modules +cleanassets: + rm -rf dist/build/mv3-data dist/build/uAssets # Not real targets, just convenient for auto-completion at shell prompt compare: From fcc77e7c9245621917f2f010d8b8e2d09394a80b Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 25 Feb 2024 20:39:21 -0500 Subject: [PATCH 011/180] [mv3] Add ability to manually add filters to a ruleset --- platform/mv3/make-rulesets.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/platform/mv3/make-rulesets.js b/platform/mv3/make-rulesets.js index 59ed88f0fc4c8..bf6bfed452dd8 100644 --- a/platform/mv3/make-rulesets.js +++ b/platform/mv3/make-rulesets.js @@ -996,6 +996,10 @@ async function rulesetFromURLs(assetDetails) { assetDetails.text = text; } + if ( Array.isArray(assetDetails.filters) ) { + assetDetails.text += '\n' + assetDetails.filters.join('\n'); + } + const extensionPaths = []; for ( const [ fname, details ] of redirectResourcesMap ) { const path = `/web_accessible_resources/${fname}`; @@ -1169,6 +1173,8 @@ async function main() { urls: contentURLs, dnrURL: 'https://ublockorigin.github.io/uAssets/dnr/default.json', homeURL: 'https://github.com/uBlockOrigin/uAssets', + filters: [ + ], }); // Regional rulesets From f7e00e422324f87ca7aef993031faeb26186035f Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 25 Feb 2024 20:49:25 -0500 Subject: [PATCH 012/180] [mv3] Fix rule id-salvaging task --- tools/make-mv3.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/make-mv3.sh b/tools/make-mv3.sh index a5598ff540898..39b0c3879e986 100755 --- a/tools/make-mv3.sh +++ b/tools/make-mv3.sh @@ -117,10 +117,6 @@ if [ "$QUICK" != "yes" ]; then cp "$UBO_DIR"/src/web_accessible_resources/* "$TMPDIR"/web_accessible_resources/ cd "$TMPDIR" node --no-warnings make-rulesets.js output="$DES" platform="$PLATFORM" - if [ -n "$BEFORE" ]; then - echo "*** uBOLite.mv3: salvaging rule ids to minimize diff size" - node --no-warnings salvage-ruleids.mjs before="$BEFORE"/"$PLATFORM" after="$DES" - fi cd - > /dev/null rm -rf "$TMPDIR" fi @@ -129,6 +125,10 @@ echo "*** uBOLite.mv3: extension ready" echo "Extension location: $DES/" if [ "$FULL" = "yes" ]; then + if [ -n "$BEFORE" ]; then + echo "*** uBOLite.mv3: salvaging rule ids to minimize diff size" + node salvage-ruleids.mjs before="$BEFORE"/"$PLATFORM" after="$DES" + fi EXTENSION="zip" if [ "$PLATFORM" = "firefox" ]; then EXTENSION="xpi" From 2262a129ecc2beca194b4def8349396e788e673a Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 26 Feb 2024 16:08:12 -0500 Subject: [PATCH 013/180] Don't match network filter-derived regexes against non-network URIs Context: element picker Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/3142 --- src/js/scriptlets/epicker.js | 44 +++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/js/scriptlets/epicker.js b/src/js/scriptlets/epicker.js index 80489e81d58ca..9e75b53ccc4cc 100644 --- a/src/js/scriptlets/epicker.js +++ b/src/js/scriptlets/epicker.js @@ -619,6 +619,21 @@ const filterToDOMInterface = (( ) => { const reCaret = '(?:[^%.0-9a-z_-]|$)'; const rePseudoElements = /:(?::?after|:?before|:[a-z-]+)$/; + const matchElemToRegex = (elem, re) => { + const srcProp = netFilter1stSources[elem.localName]; + let src = elem[srcProp]; + if ( src instanceof SVGAnimatedString ) { + src = src.baseVal; + } + if ( typeof src === 'string' && /^https?:\/\//.test(src) ) { + if ( re.test(src) ) { return srcProp; } + } + src = elem.currentSrc; + if ( typeof src === 'string' && /^https?:\/\//.test(src) ) { + if ( re.test(src) ) { return srcProp; } + } + }; + // Net filters: we need to lookup manually -- translating into a foolproof // CSS selector is just not possible. // @@ -672,28 +687,21 @@ const filterToDOMInterface = (( ) => { // Lookup by tag names. // https://github.com/uBlockOrigin/uBlock-issues/issues/2260 // Maybe get to the actual URL indirectly. + // + // https://github.com/uBlockOrigin/uBlock-issues/issues/3142 + // Don't try to match against non-network URIs. const elems = document.querySelectorAll( Object.keys(netFilter1stSources).join() ); for ( const elem of elems ) { - const srcProp = netFilter1stSources[elem.localName]; - let src = elem[srcProp]; - if ( src instanceof SVGAnimatedString ) { - src = src.baseVal; - } - 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, - }); - } + const srcProp = matchElemToRegex(elem, reFilter); + if ( srcProp === undefined ) { continue; } + out.push({ + elem, + src: srcProp, + opt: filterTypes[elem.localName], + style: vAPI.hideStyle, + }); } // Find matching background image in current set of candidate elements. From 086766a924a42affccfd83373a869e888e2ea8a4 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 26 Feb 2024 16:50:11 -0500 Subject: [PATCH 014/180] Redesign cache storage In uBO, the "cache storage" is used to save resources which can be safely discarded, though at the cost of having to fetch or recompute them again. Extension storage (browser.storage.local) is now always used as cache storage backend. This has always been the default for Chromium-based browsers. For Firefox-based browsers, IndexedDB was used as backend for cache storage, with fallback to extension storage when using Firefox in private mode by default. Extension storage is reliable since it works in all contexts, though it may not be the most performant one. To speed-up loading of resources from extension storage, uBO will now make use of Cache API storage, which will mirror content of key assets saved to extension storage. Typically loading resources from Cache API is faster than loading the same resources from the extension storage. Only resources which must be loaded in memory as fast as possible will make use of the Cache API storage layered on top of the extension storage. Compiled filter lists and memory snapshot of filtering engines (aka "selfies") will be mirrored to the Cache API storage, since these must be loaded into memory as fast as possible, and reloading filter lists from their compiled counterpart is a common operation. This new design makes it now seamless to work in permanent private mode for Firefox-based browsers, since extension storage now always contains cache-related assets. Support for IndexedDB is removed for the time being, except to support migration of cached assets the first time uBO runs with the new cache storage design. In order to easily support all choices of storage, a new serializer has been introduced, which is capable of serializing/deserializing structure-cloneable data to/from a JS string. Because of this new serializer, JS data structures can be stored directly from their native representation, and deserialized directly to their native representation from uBO's point of view, since the serialization occurs (if needed) only at the storage interface level. This new serializer simplifies many code paths where data structures such as Set, Map, TypedArray, RegExp, etc. had to be converted in a disparate manner to be able to persist them to extension storage. The new serializer supports workers and LZ4 compression. These can be configured through advanced settings. With this new layered design, it's possible to introduce more storage layers if measured as beneficial (i.e. maybe browser.storage.session) References: - https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/local - https://developer.mozilla.org/en-US/docs/Web/API/Cache - https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm --- platform/common/vapi-background.js | 23 +- src/js/assets.js | 157 +-- src/js/background.js | 3 +- src/js/base64-custom.js | 103 +- src/js/biditrie.js | 31 +- src/js/cachestorage.js | 685 +++++---- src/js/cosmetic-filtering.js | 20 +- src/js/hntrie.js | 35 +- src/js/messaging.js | 34 +- src/js/redirect-engine.js | 43 +- src/js/reverselookup.js | 2 +- src/js/scriptlet-filtering-core.js | 2 +- src/js/scuo-serializer.js | 1307 ++++++++++++++++++ src/js/start.js | 54 +- src/js/static-ext-filtering-db.js | 12 +- src/js/static-ext-filtering.js | 45 +- src/js/static-net-filtering.js | 180 +-- src/js/storage.js | 94 +- src/lib/publicsuffixlist/publicsuffixlist.js | 10 +- 19 files changed, 1916 insertions(+), 924 deletions(-) create mode 100644 src/js/scuo-serializer.js diff --git a/platform/common/vapi-background.js b/platform/common/vapi-background.js index 54148039eb6af..08cfd58720470 100644 --- a/platform/common/vapi-background.js +++ b/platform/common/vapi-background.js @@ -1671,10 +1671,7 @@ vAPI.cloud = (( ) => { const push = async function(details) { const { datakey, data, encode } = details; - if ( - data === undefined || - typeof data === 'string' && data === '' - ) { + if ( data === undefined || typeof data === 'string' && data === '' ) { return deleteChunks(datakey, 0); } const item = { @@ -1682,10 +1679,9 @@ vAPI.cloud = (( ) => { tstamp: Date.now(), data, }; - const json = JSON.stringify(item); const encoded = encode instanceof Function - ? await encode(json) - : json; + ? await encode(item) + : JSON.stringify(item); // Chunkify taking into account QUOTA_BYTES_PER_ITEM: // https://developer.chrome.com/extensions/storage#property-sync @@ -1750,13 +1746,16 @@ vAPI.cloud = (( ) => { i += 1; } encoded = encoded.join(''); - const json = decode instanceof Function - ? await decode(encoded) - : encoded; + let entry = null; try { - entry = JSON.parse(json); - } catch(ex) { + if ( decode instanceof Function ) { + entry = await decode(encoded) || null; + } + if ( typeof entry === 'string' ) { + entry = JSON.parse(entry); + } + } catch(_) { } return entry; }; diff --git a/src/js/assets.js b/src/js/assets.js index 5a550dfb4643c..6484d289d9b8e 100644 --- a/src/js/assets.js +++ b/src/js/assets.js @@ -528,12 +528,12 @@ function getAssetSourceRegistry() { assetSourceRegistryPromise = cacheStorage.get( 'assetSourceRegistry' ).then(bin => { - if ( - bin instanceof Object && - bin.assetSourceRegistry instanceof Object - ) { - assetSourceRegistry = bin.assetSourceRegistry; - return assetSourceRegistry; + if ( bin instanceof Object ) { + if ( bin.assetSourceRegistry instanceof Object ) { + assetSourceRegistry = bin.assetSourceRegistry; + ubolog('Loaded assetSourceRegistry'); + return assetSourceRegistry; + } } return assets.fetchText( µb.assetsBootstrapLocation || µb.assetsJsonPath @@ -543,6 +543,7 @@ function getAssetSourceRegistry() { : assets.fetchText(µb.assetsJsonPath); }).then(details => { updateAssetSourceRegistry(details.content, true); + ubolog('Loaded assetSourceRegistry'); return assetSourceRegistry; }); }); @@ -673,39 +674,27 @@ let assetCacheRegistryPromise; let assetCacheRegistry = {}; function getAssetCacheRegistry() { - 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; - }); + if ( assetCacheRegistryPromise !== undefined ) { + return assetCacheRegistryPromise; } - + assetCacheRegistryPromise = cacheStorage.get( + 'assetCacheRegistry' + ).then(bin => { + if ( bin instanceof Object === false ) { return; } + if ( bin.assetCacheRegistry instanceof Object === false ) { return; } + if ( Object.keys(assetCacheRegistry).length !== 0 ) { + return console.error('getAssetCacheRegistry(): assetCacheRegistry reassigned!'); + } + ubolog('Loaded assetCacheRegistry'); + assetCacheRegistry = bin.assetCacheRegistry; + }).then(( ) => + assetCacheRegistry + ); return assetCacheRegistryPromise; } const saveAssetCacheRegistry = (( ) => { - const save = function() { + const save = ( ) => { timer.off(); cacheStorage.set({ assetCacheRegistry }); }; @@ -726,7 +715,9 @@ async function assetCacheRead(assetKey, updateReadTime = false) { const reportBack = function(content) { if ( content instanceof Blob ) { content = ''; } const details = { assetKey, content }; - if ( content === '' ) { details.error = 'ENOTFOUND'; } + if ( content === '' || content === undefined ) { + details.error = 'ENOTFOUND'; + } return details; }; @@ -742,17 +733,11 @@ async function assetCacheRead(assetKey, updateReadTime = false) { ) + ' ms'; } - if ( - bin instanceof Object === false || - bin.hasOwnProperty(internalKey) === false - ) { - return reportBack(''); - } + if ( bin instanceof Object === false ) { return reportBack(''); } + if ( bin.hasOwnProperty(internalKey) === false ) { return reportBack(''); } const entry = assetCacheRegistry[assetKey]; - if ( entry === undefined ) { - return reportBack(''); - } + if ( entry === undefined ) { return reportBack(''); } entry.readTime = Date.now(); if ( updateReadTime ) { @@ -762,34 +747,22 @@ async function assetCacheRead(assetKey, updateReadTime = false) { return reportBack(bin[internalKey]); } -async function assetCacheWrite(assetKey, details) { - let content = ''; - let options = {}; - if ( typeof details === 'string' ) { - content = details; - } else if ( details instanceof Object ) { - content = details.content || ''; - options = details; - } - - if ( content === '' ) { +async function assetCacheWrite(assetKey, content, options = {}) { + if ( content === '' || content === undefined ) { return assetCacheRemove(assetKey); } - const cacheDict = await getAssetCacheRegistry(); + const { resourceTime, url } = options; - let entry = cacheDict[assetKey]; - if ( entry === undefined ) { - entry = cacheDict[assetKey] = {}; - } - entry.writeTime = entry.readTime = Date.now(); - entry.resourceTime = options.resourceTime || 0; - if ( typeof options.url === 'string' ) { - entry.remoteURL = options.url; - } - cacheStorage.set({ - assetCacheRegistry, - [`cache/${assetKey}`]: content + getAssetCacheRegistry().then(cacheDict => { + const entry = cacheDict[assetKey] || {}; + cacheDict[assetKey] = entry; + entry.writeTime = entry.readTime = Date.now(); + entry.resourceTime = resourceTime || 0; + if ( typeof url === 'string' ) { + entry.remoteURL = url; + } + cacheStorage.set({ assetCacheRegistry, [`cache/${assetKey}`]: content }); }); const result = { assetKey, content }; @@ -800,21 +773,31 @@ async function assetCacheWrite(assetKey, details) { return result; } -async function assetCacheRemove(pattern) { +async function assetCacheRemove(pattern, options = {}) { 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; + if ( pattern instanceof RegExp ) { + if ( pattern.test(assetKey) === false ) { continue; } + } else if ( typeof pattern === 'string' ) { + if ( assetKey !== pattern ) { continue; } } removedEntries.push(assetKey); - removedContent.push('cache/' + assetKey); + removedContent.push(`cache/${assetKey}`); delete cacheDict[assetKey]; } + if ( options.janitor && pattern instanceof RegExp ) { + const re = new RegExp( + pattern.source.replace(/^\^/, 'cache\/'), + pattern.flags + ); + const keys = await cacheStorage.keys(re); + for ( const key of keys ) { + removedContent.push(key); + ubolog(`Removing stray ${key}`); + } + } if ( removedContent.length !== 0 ) { await Promise.all([ cacheStorage.remove(removedContent), @@ -980,8 +963,7 @@ assets.get = async function(assetKey, options = {}) { } if ( details.content === '' ) { continue; } if ( reIsExternalPath.test(contentURL) && options.dontCache !== true ) { - assetCacheWrite(assetKey, { - content: details.content, + assetCacheWrite(assetKey, details.content, { url: contentURL, silent: options.silent === true, }); @@ -1057,8 +1039,7 @@ async function getRemote(assetKey, options = {}) { } // Success - assetCacheWrite(assetKey, { - content: result.content, + assetCacheWrite(assetKey, result.content, { url: contentURL, resourceTime: result.resourceTime || 0, }); @@ -1101,6 +1082,17 @@ assets.put = async function(assetKey, content) { /******************************************************************************/ +assets.toCache = async function(assetKey, content) { + return assetCacheWrite(assetKey, content); +}; + +assets.fromCache = async function(assetKey) { + const details = await assetCacheRead(assetKey); + return details && details.content; +}; + +/******************************************************************************/ + assets.metadata = async function() { await Promise.all([ getAssetSourceRegistry(), @@ -1147,8 +1139,8 @@ assets.metadata = async function() { assets.purge = assetCacheMarkAsDirty; -assets.remove = function(pattern) { - return assetCacheRemove(pattern); +assets.remove = function(...args) { + return assetCacheRemove(...args); }; assets.rmrf = function() { @@ -1300,8 +1292,7 @@ async function diffUpdater() { 'Diff-Path', 'Diff-Expires', ]); - assetCacheWrite(data.assetKey, { - content: data.text, + assetCacheWrite(data.assetKey, data.text, { resourceTime: metadata.lastModified || 0, }); metadata.diffUpdated = true; diff --git a/src/js/background.js b/src/js/background.js index 470a13177f827..80bab5a95168e 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -56,6 +56,7 @@ const hiddenSettingsDefault = { blockingProfiles: '11111/#F00 11010/#C0F 11001/#00F 00001', cacheStorageAPI: 'unset', cacheStorageCompression: true, + cacheStorageMultithread: 2, cacheControlForFirefox1376932: 'no-cache, no-store, must-revalidate', cloudStorageCompression: true, cnameIgnoreList: 'unset', @@ -181,7 +182,7 @@ const µBlock = { // jshint ignore:line // Read-only systemSettings: { compiledMagic: 57, // Increase when compiled format changes - selfieMagic: 57, // Increase when selfie format changes + selfieMagic: 58, // Increase when selfie format changes }, // https://github.com/uBlockOrigin/uBlock-issues/issues/759#issuecomment-546654501 diff --git a/src/js/base64-custom.js b/src/js/base64-custom.js index 34141b8a0f512..0d9a43fa2275d 100644 --- a/src/js/base64-custom.js +++ b/src/js/base64-custom.js @@ -46,105 +46,6 @@ const digitToVal = new Uint8Array(128); } } -// 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 @@ -154,7 +55,7 @@ const sparseBase64 = { // ArrayBuffer fails, the content of the resulting Uint8Array is // non-sensical. WASM-related? -const denseBase64 = { +export const denseBase64 = { magic: 'DenseBase64_1', encode: function(input) { @@ -242,5 +143,3 @@ const denseBase64 = { }; /******************************************************************************/ - -export { denseBase64, sparseBase64 }; diff --git a/src/js/biditrie.js b/src/js/biditrie.js index d0f64ee5b54b6..1329316384d89 100644 --- a/src/js/biditrie.js +++ b/src/js/biditrie.js @@ -576,34 +576,19 @@ class BidiTrieContainer { }; } - 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 - ) + toSelfie() { + return this.buf32.subarray( + 0, + this.buf32[CHAR1_SLOT] + 3 >>> 2 ); } - unserialize(selfie, decoder) { - const shouldDecode = typeof selfie === 'string'; - let byteLength = shouldDecode - ? decoder.decodeSize(selfie) - : selfie.length << 2; + fromSelfie(selfie) { + if ( selfie instanceof Uint32Array === false ) { return false; } + let byteLength = selfie.length << 2; if ( byteLength === 0 ) { return false; } this.reallocateBuf(byteLength); - if ( shouldDecode ) { - decoder.decode(selfie, this.buf8.buffer); - } else { - this.buf32.set(selfie); - } + this.buf32.set(selfie); return true; } diff --git a/src/js/cachestorage.js b/src/js/cachestorage.js index ef056af95eb14..e70fc322996a0 100644 --- a/src/js/cachestorage.js +++ b/src/js/cachestorage.js @@ -19,179 +19,362 @@ Home: https://github.com/gorhill/uBlock */ -/* global browser, IDBDatabase, indexedDB */ +/* global browser, indexedDB */ 'use strict'; /******************************************************************************/ import lz4Codec from './lz4.js'; -import µb from './background.js'; import webext from './webext.js'; +import µb from './background.js'; +import { ubolog } from './console.js'; +import * as scuo from './scuo-serializer.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'; +const extensionStorage = webext.storage.local; + +const keysFromGetArg = arg => { + if ( arg === null || arg === undefined ) { return []; } + const type = typeof arg; + if ( type === 'string' ) { return [ arg ]; } + if ( Array.isArray(arg) ) { return arg; } + if ( type !== 'object' ) { return; } + return Object.keys(arg); +}; + +// Cache API is subject to quota so we will use it only for what is key +// performance-wise +const shouldCache = bin => { + const out = {}; + for ( const key of Object.keys(bin) ) { + if ( key.startsWith('cache/') ) { + if ( /^cache\/(compiled|selfie)\//.test(key) === false ) { continue; } + } + out[key] = bin[key]; + } + return out; +}; + +/******************************************************************************* + * + * Extension storage + * + * Always available. + * + * */ + +const cacheStorage = (( ) => { + + const LARGE = 65536; + + const compress = async (key, data) => { + const isLarge = typeof data === 'string' && data.length >= LARGE; + const µbhs = µb.hiddenSettings; + const after = await scuo.serializeAsync(data, { + compress: isLarge && µbhs.cacheStorageCompression, + multithreaded: isLarge && µbhs.cacheStorageMultithread || 0, + }); + return { key, data: after }; + }; -// Default to webext storage. -const storageLocal = webext.storage.local; - -let storageReadyResolve; -const storageReadyPromise = new Promise(resolve => { - storageReadyResolve = resolve; -}); - -const cacheStorage = { - name: 'browser.storage.local', - get(...args) { - return storageReadyPromise.then(( ) => - storageLocal.get(...args).catch(reason => { - console.log(reason); - }) - ); - }, - set(...args) { - return storageReadyPromise.then(( ) => - storageLocal.set(...args).catch(reason => { - console.log(reason); - }) - ); - }, - remove(...args) { - return storageReadyPromise.then(( ) => - storageLocal.remove(...args).catch(reason => { - console.log(reason); - }) - ); - }, - clear(...args) { - return storageReadyPromise.then(( ) => - storageLocal.clear(...args).catch(reason => { - console.log(reason); - }) - ); - }, - select: function(selectedBackend) { - let actualBackend = selectedBackend; - if ( actualBackend === undefined || actualBackend === 'unset' ) { - actualBackend = vAPI.webextFlavor.soup.has('firefox') - ? 'indexedDB' - : 'browser.storage.local'; + const decompress = async (key, data) => { + if ( scuo.canDeserialize(data) === false ) { + return { key, data }; } - if ( actualBackend === 'indexedDB' ) { - return selectIDB().then(success => { - if ( success || selectedBackend === 'indexedDB' ) { - clearWebext(); - storageReadyResolve(); - return 'indexedDB'; + const isLarge = data.length >= LARGE; + const after = await scuo.deserializeAsync(data, { + multithreaded: isLarge && µb.hiddenSettings.cacheStorageMultithread || 0, + }); + return { key, data: after }; + }; + + return { + name: 'browser.storage.local', + + get(arg) { + const keys = arg; + return cacheAPI.get(keysFromGetArg(arg)).then(bin => { + if ( bin !== undefined ) { return bin; } + return extensionStorage.get(keys).catch(reason => { + ubolog(reason); + }); + }).then(bin => { + if ( bin instanceof Object === false ) { return bin; } + const promises = []; + for ( const key of Object.keys(bin) ) { + promises.push(decompress(key, bin[key])); + } + return Promise.all(promises); + }).then(results => { + const bin = {}; + for ( const { key, data } of results ) { + bin[key] = data; } - clearIDB(); - storageReadyResolve(); - return 'browser.storage.local'; + return bin; + }).catch(reason => { + ubolog(reason); }); - } - if ( actualBackend === 'browser.storage.local' ) { - clearIDB(); - } - storageReadyResolve(); - return Promise.resolve('browser.storage.local'); - - }, - error: undefined -}; + }, + + async keys(regex) { + const results = await Promise.all([ + cacheAPI.keys(regex), + extensionStorage.get(null).catch(( ) => {}), + ]); + const keys = new Set(results[0]); + const bin = results[1] || {}; + for ( const key of Object.keys(bin) ) { + if ( regex && regex.test(key) === false ) { continue; } + keys.add(key); + } + return keys; + }, + + async set(keyvalStore) { + const keys = Object.keys(keyvalStore); + if ( keys.length === 0 ) { return; } + const promises = []; + for ( const key of keys ) { + promises.push(compress(key, keyvalStore[key])); + } + const results = await Promise.all(promises); + const serializedStore = {}; + for ( const { key, data } of results ) { + serializedStore[key] = data; + } + cacheAPI.set(shouldCache(serializedStore)); + return extensionStorage.set(serializedStore).catch(reason => { + ubolog(reason); + }); + }, + + remove(...args) { + cacheAPI.remove(...args); + return extensionStorage.remove(...args).catch(reason => { + ubolog(reason); + }); + }, + + clear(...args) { + cacheAPI.clear(...args); + return extensionStorage.clear(...args).catch(reason => { + ubolog(reason); + }); + }, + + async migrate(cacheAPI) { + if ( cacheAPI === 'browser.storage.local' ) { return; } + if ( cacheAPI !== 'indexedDB' ) { + if ( vAPI.webextFlavor.soup.has('firefox') === false ) { return; } + } + if ( browser.extension.inIncognitoContext ) { return; } + // Copy all items to new cache storage + const bin = await idbStorage.get(null); + if ( typeof bin !== 'object' || bin === null ) { return; } + const toMigrate = []; + for ( const key of Object.keys(bin) ) { + if ( key.startsWith('cache/selfie/') ) { continue; } + ubolog(`Migrating ${key}=${JSON.stringify(bin[key]).slice(0,32)}`); + toMigrate.push(cacheStorage.set({ [key]: bin[key] })); + } + idbStorage.clear(); + return Promise.all(toMigrate); + }, + + error: undefined + }; +})(); // Not all platforms support getBytesInUse -if ( storageLocal.getBytesInUse instanceof Function ) { +if ( extensionStorage.getBytesInUse instanceof Function ) { cacheStorage.getBytesInUse = function(...args) { - return storageLocal.getBytesInUse(...args).catch(reason => { - console.log(reason); + return extensionStorage.getBytesInUse(...args).catch(reason => { + ubolog(reason); }); }; } -// Reassign API entries to that of indexedDB-based ones -const selectIDB = async function() { - let db; - let dbPromise; +/******************************************************************************* + * + * Cache API + * + * Purpose is to mirror cache-related items from extension storage, as its + * read/write operations are faster. May not be available/populated in + * private/incognito mode. + * + * */ + +const cacheAPI = (( ) => { + const caches = globalThis.caches; + const cacheStoragePromise = new Promise(resolve => { + if ( typeof caches !== 'object' || caches === null ) { + ubolog('CacheStorage API not available'); + resolve(null); + return; + } + resolve(caches.open(STORAGE_NAME).catch(reason => { + ubolog(reason); + })); + }); - const noopfn = function () { + const urlPrefix = 'https://ublock0.invalid/'; + + const keyToURL = key => + `${urlPrefix}${encodeURIComponent(key)}`; + + const urlToKey = url => + decodeURIComponent(url.slice(urlPrefix.length)); + + const getOne = async key => { + const cache = await cacheStoragePromise; + if ( cache === null ) { return; } + return cache.match(keyToURL(key)).then(response => { + if ( response instanceof Response === false ) { return; } + return response.text(); + }).then(text => { + if ( text === undefined ) { return; } + return { key, text }; + }).catch(reason => { + ubolog(reason); + }); }; - const disconnect = function() { - dbTimer.off(); - if ( db instanceof IDBDatabase ) { - db.close(); - db = undefined; - } + const getAll = async ( ) => { + const cache = await cacheStoragePromise; + if ( cache === null ) { return; } + return cache.keys().then(requests => { + const promises = []; + for ( const request of requests ) { + promises.push(getOne(urlToKey(request.url))); + } + return Promise.all(promises); + }).then(responses => { + const bin = {}; + for ( const response of responses ) { + if ( response === undefined ) { continue; } + bin[response.key] = response.text; + } + return bin; + }).catch(reason => { + ubolog(reason); + }); }; - const dbTimer = vAPI.defer.create(( ) => { - disconnect(); - }); + const setOne = async (key, text) => { + if ( text === undefined ) { return removeOne(key); } + const blob = new Blob([ text ], { type: 'text/plain;charset=utf-8'}); + const cache = await cacheStoragePromise; + if ( cache === null ) { return; } + return cache + .put(keyToURL(key), new Response(blob)) + .catch(reason => { + ubolog(reason); + }); + }; + + const removeOne = async key => { + const cache = await cacheStoragePromise; + if ( cache === null ) { return; } + return cache.delete(keyToURL(key)).catch(reason => { + ubolog(reason); + }); + }; + + return { + async get(arg) { + const keys = keysFromGetArg(arg); + if ( keys === undefined ) { return; } + if ( keys.length === 0 ) { + return getAll(); + } + const bin = {}; + const toFetch = keys.slice(); + const hasDefault = typeof arg === 'object' && Array.isArray(arg) === false; + for ( let i = 0; i < toFetch.length; i++ ) { + const key = toFetch[i]; + if ( hasDefault && arg[key] !== undefined ) { + bin[key] = arg[key]; + } + toFetch[i] = getOne(key); + } + const responses = await Promise.all(toFetch); + for ( const response of responses ) { + if ( response instanceof Object === false ) { continue; } + const { key, text } = response; + if ( typeof key !== 'string' ) { continue; } + if ( typeof text !== 'string' ) { continue; } + bin[key] = text; + } + if ( Object.keys(bin).length === 0 ) { return; } + return bin; + }, + + async keys(regex) { + const cache = await cacheStoragePromise; + if ( cache === null ) { return []; } + return cache.keys().then(requests => + requests.map(r => urlToKey(r.url)) + .filter(k => regex === undefined || regex.test(k)) + ).catch(( ) => []); + }, + + async set(keyvalStore) { + const keys = Object.keys(keyvalStore); + if ( keys.length === 0 ) { return; } + const promises = []; + for ( const key of keys ) { + promises.push(setOne(key, keyvalStore[key])); + } + return Promise.all(promises); + }, + + async remove(keys) { + const toRemove = []; + if ( typeof keys === 'string' ) { + toRemove.push(removeOne(keys)); + } else if ( Array.isArray(keys) ) { + for ( const key of keys ) { + toRemove.push(removeOne(key)); + } + } + return Promise.all(toRemove); + }, - const keepAlive = function() { - dbTimer.offon(Math.max( - µb.hiddenSettings.autoUpdateAssetFetchPeriod * 2 * 1000, - 180000 - )); + async clear() { + return globalThis.caches.delete(STORAGE_NAME).catch(reason => { + ubolog(reason); + }); + }, }; +})(); - // 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). +/******************************************************************************* + * + * IndexedDB + * + * Deprecated, exists only for the purpose of migrating from older versions. + * + * */ + +const idbStorage = (( ) => { + let dbPromise; const getDb = function() { - keepAlive(); - if ( db !== undefined ) { - return Promise.resolve(db); - } - if ( dbPromise !== undefined ) { - return dbPromise; - } + 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); + ubolog(req.error); req = undefined; } } catch(ex) { } if ( req === undefined ) { - db = null; - dbPromise = undefined; return resolve(null); } req.onupgradeneeded = function(ev) { @@ -215,24 +398,16 @@ const selectIDB = async function() { req.onsuccess = function(ev) { if ( resolve === undefined ) { return; } req = undefined; - db = ev.target.result; - dbPromise = undefined; - resolve(db); + resolve(ev.target.result); 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; }; vAPI.defer.once(5000).then(( ) => { if ( resolve === undefined ) { return; } - db = null; - dbPromise = undefined; resolve(null); resolve = undefined; }); @@ -253,60 +428,12 @@ const selectIDB = async function() { }); }; - 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(); } @@ -341,190 +468,40 @@ const selectIDB = async function() { if ( entry.value instanceof Blob === false ) { return; } promises.push(decompress(keyvalStore, key, value)); }).catch(reason => { - console.info(`cacheStorage.getAllFromDb() failed: ${reason}`); + ubolog(`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; + callback = ()=>{}; } 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(); + db.close(); + indexedDB.deleteDatabase(STORAGE_NAME); + callback(); } catch(reason) { - console.info(`cacheStorage.clearDb() failed: ${reason}`); callback(); } }; - await getDb(); - if ( !db ) { return false; } - - cacheStorage.name = 'indexedDB'; - cacheStorage.get = function get(keys) { - return storageReadyPromise.then(( ) => - 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 storageReadyPromise.then(( ) => - new Promise(resolve => { - putToDb(keys, details => resolve(details)); - }) - ); - }; - cacheStorage.remove = function remove(keys) { - return storageReadyPromise.then(( ) => - new Promise(resolve => { - deleteFromDb(keys, ( ) => resolve()); - }) - ); - }; - cacheStorage.clear = function clear() { - return storageReadyPromise.then(( ) => - new Promise(resolve => { + return { + get: function get() { + return new Promise(resolve => { + return getAllFromDb(bin => resolve(bin)); + }); + }, + 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() { - let bin; - try { - bin = await webext.storage.local.get('assetCacheRegistry'); - } catch(ex) { - console.error(ex); - } - if ( bin instanceof Object === false ) { return; } - if ( bin.assetCacheRegistry instanceof Object === false ) { return; } - const toRemove = [ - 'assetCacheRegistry', - 'assetSourceRegistry', - ]; - 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) { - } -}; +})(); /******************************************************************************/ diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index f4782bc375407..04fc93a8fa28f 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -292,7 +292,7 @@ FilterContainer.prototype.reset = function() { this.highlyGeneric.complex.str = ''; this.highlyGeneric.complex.mru.reset(); - this.selfieVersion = 1; + this.selfieVersion = 2; }; /******************************************************************************/ @@ -576,9 +576,11 @@ FilterContainer.prototype.toSelfie = function() { acceptedCount: this.acceptedCount, discardedCount: this.discardedCount, specificFilters: this.specificFilters.toSelfie(), - lowlyGeneric: Array.from(this.lowlyGeneric), - highSimpleGenericHideArray: Array.from(this.highlyGeneric.simple.dict), - highComplexGenericHideArray: Array.from(this.highlyGeneric.complex.dict), + lowlyGeneric: this.lowlyGeneric, + highSimpleGenericHideDict: this.highlyGeneric.simple.dict, + highSimpleGenericHideStr: this.highlyGeneric.simple.str, + highComplexGenericHideDict: this.highlyGeneric.complex.dict, + highComplexGenericHideStr: this.highlyGeneric.complex.str, }; }; @@ -593,11 +595,11 @@ FilterContainer.prototype.fromSelfie = function(selfie) { this.acceptedCount = selfie.acceptedCount; this.discardedCount = selfie.discardedCount; this.specificFilters.fromSelfie(selfie.specificFilters); - this.lowlyGeneric = new Map(selfie.lowlyGeneric); - 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.lowlyGeneric = selfie.lowlyGeneric; + this.highlyGeneric.simple.dict = selfie.highSimpleGenericHideDict; + this.highlyGeneric.simple.str = selfie.highSimpleGenericHideStr; + this.highlyGeneric.complex.dict = selfie.highComplexGenericHideDict; + this.highlyGeneric.complex.str = selfie.highComplexGenericHideStr; this.frozen = true; }; diff --git a/src/js/hntrie.js b/src/js/hntrie.js index e8031a651bfcd..cc726db5d87bb 100644 --- a/src/js/hntrie.js +++ b/src/js/hntrie.js @@ -445,28 +445,17 @@ class HNTrieContainer { }; } - 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 - ) + toSelfie() { + return this.buf32.subarray( + 0, + this.buf32[CHAR1_SLOT] + 3 >>> 2 ); } - unserialize(selfie, decoder) { + fromSelfie(selfie) { + if ( selfie instanceof Uint32Array === false ) { return false; } this.needle = ''; - const shouldDecode = typeof selfie === 'string'; - let byteLength = shouldDecode - ? decoder.decodeSize(selfie) - : selfie.length << 2; + let byteLength = selfie.length << 2; if ( byteLength === 0 ) { return false; } byteLength = roundToPageSize(byteLength); if ( this.wasmMemory !== null ) { @@ -477,14 +466,10 @@ class HNTrieContainer { 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); + } else { + this.buf32 = selfie; + this.buf = new Uint8Array(this.buf32.buffer); } // https://github.com/uBlockOrigin/uBlock-issues/issues/2925 this.buf[255] = 0; diff --git a/src/js/messaging.js b/src/js/messaging.js index ec3f0f4e5ff24..38b03a4093460 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -45,6 +45,7 @@ import { dnrRulesetFromRawLists } from './static-dnr-filtering.js'; import { i18n$ } from './i18n.js'; import { redirectEngine } from './redirect-engine.js'; import * as sfp from './static-filtering-parser.js'; +import * as scuo from './scuo-serializer.js'; import { permanentFirewall, @@ -925,21 +926,6 @@ const fromBase64 = function(encoded) { 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 ) { @@ -961,15 +947,25 @@ const onMessage = function(request, sender, callback) { return; case 'cloudPull': - request.decode = decompress; + request.decode = encoded => { + if ( scuo.canDeserialize(encoded) ) { + return scuo.deserializeAsync(encoded, { thread: true }); + } + // Legacy decoding: needs to be kept around for the foreseeable future. + return lz4Codec.decode(encoded, fromBase64); + }; return vAPI.cloud.pull(request).then(result => { callback(result); }); case 'cloudPush': - if ( µb.hiddenSettings.cloudStorageCompression ) { - request.encode = compress; - } + request.encode = data => { + const options = { + compress: µb.hiddenSettings.cloudStorageCompression, + thread: true, + }; + return scuo.serializeAsync(data, options); + }; return vAPI.cloud.push(request).then(result => { callback(result); }); diff --git a/src/js/redirect-engine.js b/src/js/redirect-engine.js index 2f58066039a8d..7d70e35ee3c11 100644 --- a/src/js/redirect-engine.js +++ b/src/js/redirect-engine.js @@ -24,11 +24,7 @@ /******************************************************************************/ import redirectableResources from './redirect-resources.js'; - -import { - LineIterator, - orphanizeString, -} from './text-utils.js'; +import { LineIterator, orphanizeString } from './text-utils.js'; /******************************************************************************/ @@ -448,33 +444,22 @@ class RedirectEngine { } selfieFromResources(storage) { - storage.put( - RESOURCES_SELFIE_NAME, - JSON.stringify({ - version: RESOURCES_SELFIE_VERSION, - aliases: Array.from(this.aliases), - resources: Array.from(this.resources), - }) - ); + return storage.toCache(RESOURCES_SELFIE_NAME, { + version: RESOURCES_SELFIE_VERSION, + aliases: this.aliases, + resources: this.resources, + }); } async resourcesFromSelfie(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 ) { + const selfie = await storage.fromCache(RESOURCES_SELFIE_NAME); + if ( selfie instanceof Object === false ) { return false; } + if ( selfie.version !== RESOURCES_SELFIE_VERSION ) { return false; } + if ( selfie.aliases instanceof Map === false ) { return false; } + if ( selfie.resources instanceof Map === false ) { return false; } + this.aliases = selfie.aliases; + this.resources = selfie.resources; + for ( const [ token, entry ] of this.resources ) { this.resources.set(token, RedirectEntry.fromDetails(entry)); } return true; diff --git a/src/js/reverselookup.js b/src/js/reverselookup.js index c21ca4bb15d9b..e7bf24e94aa35 100644 --- a/src/js/reverselookup.js +++ b/src/js/reverselookup.js @@ -62,7 +62,7 @@ const stopWorker = function() { }; const workerTTLTimer = vAPI.defer.create(stopWorker); -const workerTTL = { min: 5 }; +const workerTTL = { min: 1.5 }; const initWorker = function() { if ( worker === null ) { diff --git a/src/js/scriptlet-filtering-core.js b/src/js/scriptlet-filtering-core.js index 75818eb976e6f..907844fbc198a 100644 --- a/src/js/scriptlet-filtering-core.js +++ b/src/js/scriptlet-filtering-core.js @@ -200,7 +200,7 @@ export class ScriptletFilteringEngine { } fromSelfie(selfie) { - if ( selfie instanceof Object === false ) { return false; } + if ( typeof selfie !== 'object' || selfie === null ) { return false; } if ( selfie.version !== VERSION ) { return false; } this.scriptletDB.fromSelfie(selfie); return true; diff --git a/src/js/scuo-serializer.js b/src/js/scuo-serializer.js new file mode 100644 index 0000000000000..1ffffa6bec600 --- /dev/null +++ b/src/js/scuo-serializer.js @@ -0,0 +1,1307 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2024-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'; + +/******************************************************************************* + * + * Structured-Cloneable to Unicode-Only SERIALIZER + * + * Purpose: + * + * Serialize/deserialize arbitrary JS data to/from well-formed Unicode strings. + * + * The browser does not expose an API to serialize structured-cloneable types + * into a single string. JSON.stringify() does not support complex JavaScript + * objects, and does not support references to composite types. Unless the + * data to serialize is only JS strings, it is difficult to easily switch + * from one type of storage to another. + * + * Serializing to a well-formed Unicode string allows to store structured- + * cloneable data to any storage. Not all storages support storing binary data, + * but all storages support storing Unicode strings. + * + * Structured-cloneable types: + * https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#supported_types + * + * ----------------+------------------+------------------+---------------------- + * Data types | String | JSONable | structured-cloneable + * ================+============================================================ + * document.cookie | Yes | No | No + * ----------------+------------------+------------------+---------------------- + * localStorage | Yes | No | No + * ----------------+------------------+------------------+---------------------- + * IndexedDB | Yes | Yes | Yes + * ----------------+------------------+------------------+---------------------- + * browser.storage | Yes | Yes | No + * ----------------+------------------+------------------+---------------------- + * Cache API | Yes | No | No + * ----------------+------------------+------------------+---------------------- + * + * The above table shows that only JS strings can be persisted natively to all + * types of storage. The purpose of this library is to convert + * structure-cloneable data (which is a superset of JSONable data) into a + * single JS string. The resulting string is meant to be as small as possible. + * As a result, it is not human-readable, though it contains only printable + * ASCII characters -- and possibly Unicode characters beyond ASCII. + * + * The resulting JS string will not contain characters which require escaping + * should it be converted to a JSON value. However it may contain characters + * which require escaping should it be converted to a URI component. + * + * Characteristics: + * + * - Serializes/deserializes data to/from a single well-formed Unicode string + * - Strings do not require escaping, i.e. they are stored as-is + * - Supports multiple references to same object + * - Supports reference cycles + * - Supports synchronous and asynchronous API + * - Supports usage of Worker + * - Optionally supports LZ4 compression + * + * TODO: + * + * - Harden against unexpected conditions, such as corrupted string during + * deserialization. + * - Evaluate supporting checksum. + * + * */ + +const VERSION = 1; +const SEPARATORCHAR = ' '; +const SEPARATORCHARCODE = SEPARATORCHAR.charCodeAt(0); +const SENTINELCHAR = '!'; +const SENTINELCHARCODE = SENTINELCHAR.charCodeAt(0); +const MAGICPREFIX = `UOSC_${VERSION}${SEPARATORCHAR}`; +const MAGICLZ4PREFIX = `UOSC/lz4_${VERSION}${SEPARATORCHAR}`; +const FAILMARK = Number.MAX_SAFE_INTEGER; +// Avoid characters which require escaping when serialized to JSON: +const SAFECHARS = "&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~"; +const NUMSAFECHARS = SAFECHARS.length; +const BITS_PER_SAFECHARS = Math.log2(NUMSAFECHARS); + +const { intToChar, intToCharCode, charCodeToInt } = (( ) => { + const intToChar = []; + const intToCharCode = []; + const charCodeToInt = []; + for ( let i = 0; i < NUMSAFECHARS; i++ ) { + intToChar[i] = SAFECHARS.charAt(i); + intToCharCode[i] = SAFECHARS.charCodeAt(i); + charCodeToInt[i] = 0; + } + for ( let i = NUMSAFECHARS; i < 128; i++ ) { + intToChar[i] = ''; + intToCharCode[i] = 0; + charCodeToInt[i] = 0; + } + for ( let i = 0; i < SAFECHARS.length; i++ ) { + charCodeToInt[SAFECHARS.charCodeAt(i)] = i; + } + return { intToChar, intToCharCode, charCodeToInt }; +})(); + +let iota = 1; +const I_STRING_SMALL = iota++; +const I_STRING_LARGE = iota++; +const I_ZERO = iota++; +const I_INTEGER_SMALL_POS = iota++; +const I_INTEGER_SMALL_NEG = iota++; +const I_INTEGER_LARGE_POS = iota++; +const I_INTEGER_LARGE_NEG = iota++; +const I_BOOL_FALSE = iota++; +const I_BOOL_TRUE = iota++; +const I_NULL = iota++; +const I_UNDEFINED = iota++; +const I_FLOAT = iota++; +const I_REGEXP = iota++; +const I_DATE = iota++; +const I_REFERENCE = iota++; +const I_SMALL_OBJECT = iota++; +const I_LARGE_OBJECT = iota++; +const I_ARRAY_SMALL = iota++; +const I_ARRAY_LARGE = iota++; +const I_SET_SMALL = iota++; +const I_SET_LARGE = iota++; +const I_MAP_SMALL = iota++; +const I_MAP_LARGE = iota++; +const I_ARRAYBUFFER = iota++; +const I_INT8ARRAY = iota++; +const I_UINT8ARRAY = iota++; +const I_UINT8CLAMPEDARRAY = iota++; +const I_INT16ARRAY = iota++; +const I_UINT16ARRAY = iota++; +const I_INT32ARRAY = iota++; +const I_UINT32ARRAY = iota++; +const I_FLOAT32ARRAY = iota++; +const I_FLOAT64ARRAY = iota++; +const I_DATAVIEW = iota++; + +const C_STRING_SMALL = intToChar[I_STRING_SMALL]; +const C_STRING_LARGE = intToChar[I_STRING_LARGE]; +const C_ZERO = intToChar[I_ZERO]; +const C_INTEGER_SMALL_POS = intToChar[I_INTEGER_SMALL_POS]; +const C_INTEGER_SMALL_NEG = intToChar[I_INTEGER_SMALL_NEG]; +const C_INTEGER_LARGE_POS = intToChar[I_INTEGER_LARGE_POS]; +const C_INTEGER_LARGE_NEG = intToChar[I_INTEGER_LARGE_NEG]; +const C_BOOL_FALSE = intToChar[I_BOOL_FALSE]; +const C_BOOL_TRUE = intToChar[I_BOOL_TRUE]; +const C_NULL = intToChar[I_NULL]; +const C_UNDEFINED = intToChar[I_UNDEFINED]; +const C_FLOAT = intToChar[I_FLOAT]; +const C_REGEXP = intToChar[I_REGEXP]; +const C_DATE = intToChar[I_DATE]; +const C_REFERENCE = intToChar[I_REFERENCE]; +const C_SMALL_OBJECT = intToChar[I_SMALL_OBJECT]; +const C_LARGE_OBJECT = intToChar[I_LARGE_OBJECT]; +const C_ARRAY_SMALL = intToChar[I_ARRAY_SMALL]; +const C_ARRAY_LARGE = intToChar[I_ARRAY_LARGE]; +const C_SET_SMALL = intToChar[I_SET_SMALL]; +const C_SET_LARGE = intToChar[I_SET_LARGE]; +const C_MAP_SMALL = intToChar[I_MAP_SMALL]; +const C_MAP_LARGE = intToChar[I_MAP_LARGE]; +const C_ARRAYBUFFER = intToChar[I_ARRAYBUFFER]; +const C_INT8ARRAY = intToChar[I_INT8ARRAY]; +const C_UINT8ARRAY = intToChar[I_UINT8ARRAY]; +const C_UINT8CLAMPEDARRAY = intToChar[I_UINT8CLAMPEDARRAY]; +const C_INT16ARRAY = intToChar[I_INT16ARRAY]; +const C_UINT16ARRAY = intToChar[I_UINT16ARRAY]; +const C_INT32ARRAY = intToChar[I_INT32ARRAY]; +const C_UINT32ARRAY = intToChar[I_UINT32ARRAY]; +const C_FLOAT32ARRAY = intToChar[I_FLOAT32ARRAY]; +const C_FLOAT64ARRAY = intToChar[I_FLOAT64ARRAY]; +const C_DATAVIEW = intToChar[I_DATAVIEW]; + +// Just reuse already defined constants, we just need distinct values +const I_STRING = I_STRING_SMALL; +const I_NUMBER = I_FLOAT; +const I_BOOL = I_BOOL_FALSE; +const I_OBJECT = I_SMALL_OBJECT; +const I_ARRAY = I_ARRAY_SMALL; +const I_SET = I_SET_SMALL; +const I_MAP = I_MAP_SMALL; + +const typeToSerializedInt = { + 'string': I_STRING, + 'number': I_NUMBER, + 'boolean': I_BOOL, + 'object': I_OBJECT, +}; + +const xtypeToSerializedInt = { + '[object RegExp]': I_REGEXP, + '[object Date]': I_DATE, + '[object Array]': I_ARRAY, + '[object Set]': I_SET, + '[object Map]': I_MAP, + '[object ArrayBuffer]': I_ARRAYBUFFER, + '[object Int8Array]': I_INT8ARRAY, + '[object Uint8Array]': I_UINT8ARRAY, + '[object Uint8ClampedArray]': I_UINT8CLAMPEDARRAY, + '[object Int16Array]': I_INT16ARRAY, + '[object Uint16Array]': I_UINT16ARRAY, + '[object Int32Array]': I_INT32ARRAY, + '[object Uint32Array]': I_UINT32ARRAY, + '[object Float32Array]': I_FLOAT32ARRAY, + '[object Float64Array]': I_FLOAT64ARRAY, + '[object DataView]': I_DATAVIEW, +}; + +const typeToSerializedChar = { + '[object Int8Array]': C_INT8ARRAY, + '[object Uint8Array]': C_UINT8ARRAY, + '[object Uint8ClampedArray]': C_UINT8CLAMPEDARRAY, + '[object Int16Array]': C_INT16ARRAY, + '[object Uint16Array]': C_UINT16ARRAY, + '[object Int32Array]': C_INT32ARRAY, + '[object Uint32Array]': C_UINT32ARRAY, + '[object Float32Array]': C_FLOAT32ARRAY, + '[object Float64Array]': C_FLOAT64ARRAY, +}; + +const toArrayBufferViewConstructor = { + [`${I_INT8ARRAY}`]: Int8Array, + [`${I_UINT8ARRAY}`]: Uint8Array, + [`${I_UINT8CLAMPEDARRAY}`]: Uint8ClampedArray, + [`${I_INT16ARRAY}`]: Int16Array, + [`${I_UINT16ARRAY}`]: Uint16Array, + [`${I_INT32ARRAY}`]: Int32Array, + [`${I_UINT32ARRAY}`]: Uint32Array, + [`${I_FLOAT32ARRAY}`]: Float32Array, + [`${I_FLOAT64ARRAY}`]: Float64Array, + [`${I_DATAVIEW}`]: DataView, +}; + +/******************************************************************************/ + +const textDecoder = new TextDecoder(); +const textEncoder = new TextEncoder(); +const isInteger = Number.isInteger; + +const writeRefs = new Map(); +const writeBuffer = []; + +const readRefs = new Map(); +let readStr = ''; +let readPtr = 0; +let readEnd = 0; + +let refCounter = 1; + +let uint8Input = null; + +const uint8InputFromAsciiStr = s => { + if ( uint8Input === null || uint8Input.length < s.length ) { + uint8Input = new Uint8Array(s.length + 0x03FF & ~0x03FF); + } + textEncoder.encodeInto(s, uint8Input); + return uint8Input; +}; + +const isInstanceOf = (o, s) => { + return typeof o === 'object' && o !== null && ( + s === 'Object' || Object.prototype.toString.call(o) === `[object ${s}]` + ); +}; + +/******************************************************************************* + * + * A large Uint is always a positive integer (can be zero), assumed to be + * large, i.e. > NUMSAFECHARS -- but not necessarily. The serialized value has + * always at least one digit, and is always followed by a separator. + * + * */ + +const strFromLargeUint = i => { + let r = 0, s = ''; + for (;;) { + r = i % NUMSAFECHARS; + s += intToChar[r]; + i -= r; + if ( i === 0 ) { break; } + i /= NUMSAFECHARS; + } + return s + SEPARATORCHAR; +}; + +const deserializeLargeUint = ( ) => { + let c = readStr.charCodeAt(readPtr++); + let n = charCodeToInt[c]; + let m = 1; + while ( (c = readStr.charCodeAt(readPtr++)) !== SEPARATORCHARCODE ) { + m *= NUMSAFECHARS; + n += m * charCodeToInt[c]; + } + return n; +}; + +/******************************************************************************* + * + * Methods specific to ArrayBuffer objects to serialize optimally according to + * the content of the buffer. + * + * In sparse mode, number of output bytes per input int32 (4-byte) value: + * [v === zero]: 1 byte (separator) + * [v !== zero]: n digits + 1 byte (separator) + * + * */ + +const sparseValueLen = v => v !== 0 + ? (Math.log2(v) / BITS_PER_SAFECHARS | 0) + 2 + : 1; + +const analyzeArrayBuffer = arrbuf => { + const byteLength = arrbuf.byteLength; + const uint32len = byteLength >>> 2; + const uint32arr = new Uint32Array(arrbuf, 0, uint32len); + let notzeroCount = 0; + for ( let i = uint32len-1; i >= 0; i-- ) { + if ( uint32arr[i] === 0 ) { continue; } + notzeroCount = i + 1; + break; + } + const end = notzeroCount + 1 <= uint32len ? notzeroCount << 2 : byteLength; + const endUint32 = end >>> 2; + const remUint8 = end & 0b11; + const denseSize = endUint32 * 5 + (remUint8 ? remUint8 + 1 : 0); + let sparseSize = 0; + for ( let i = 0; i < endUint32; i++ ) { + sparseSize += sparseValueLen(uint32arr[i]); + if ( sparseSize > denseSize ) { + return { end, dense: true, denseSize }; + } + } + if ( remUint8 !== 0 ) { + sparseSize += 1; // sentinel + const uint8arr = new Uint8Array(arrbuf, endUint32 << 2); + for ( let i = 0; i < remUint8; i++ ) { + sparseSize += sparseValueLen(uint8arr[i]); + } + } + return { end, dense: false, sparseSize }; +}; + +const denseArrayBufferToStr = (arrbuf, details) => { + const end = details.end; + const m = end % 4; + const n = end - m; + const uin32len = n >>> 2; + const uint32arr = new Uint32Array(arrbuf, 0, uin32len); + const output = new Uint8Array(details.denseSize); + let j = 0, v = 0; + for ( let i = 0; i < uin32len; i++ ) { + v = uint32arr[i]; + output[j+0] = intToCharCode[v % NUMSAFECHARS]; + v = v / NUMSAFECHARS | 0; + output[j+1] = intToCharCode[v % NUMSAFECHARS]; + v = v / NUMSAFECHARS | 0; + output[j+2] = intToCharCode[v % NUMSAFECHARS]; + v = v / NUMSAFECHARS | 0; + output[j+3] = intToCharCode[v % NUMSAFECHARS]; + v = v / NUMSAFECHARS | 0; + output[j+4] = intToCharCode[v]; + j += 5; + } + if ( m !== 0 ) { + const uint8arr = new Uint8Array(arrbuf, n); + v = uint8arr[0]; + if ( m > 1 ) { + v += uint8arr[1] << 8; + if ( m > 2 ) { + v += uint8arr[2] << 16; + } + } + output[j+0] = intToCharCode[v % NUMSAFECHARS]; + v = v / NUMSAFECHARS | 0; + output[j+1] = intToCharCode[v % NUMSAFECHARS]; + if ( m > 1 ) { + v = v / NUMSAFECHARS | 0; + output[j+2] = intToCharCode[v % NUMSAFECHARS]; + if ( m > 2 ) { + v = v / NUMSAFECHARS | 0; + output[j+3] = intToCharCode[v % NUMSAFECHARS]; + } + } + } + return textDecoder.decode(output); +}; + +const BASE88_POW1 = NUMSAFECHARS; +const BASE88_POW2 = NUMSAFECHARS * BASE88_POW1; +const BASE88_POW3 = NUMSAFECHARS * BASE88_POW2; +const BASE88_POW4 = NUMSAFECHARS * BASE88_POW3; + +const denseArrayBufferFromStr = (denseStr, arrbuf) => { + const input = uint8InputFromAsciiStr(denseStr); + const end = denseStr.length; + const m = end % 5; + const n = end - m; + const uin32len = n / 5 * 4 >>> 2; + const uint32arr = new Uint32Array(arrbuf, 0, uin32len); + let j = 0, v = 0; + for ( let i = 0; i < n; i += 5 ) { + v = charCodeToInt[input[i+0]]; + v += charCodeToInt[input[i+1]] * BASE88_POW1; + v += charCodeToInt[input[i+2]] * BASE88_POW2; + v += charCodeToInt[input[i+3]] * BASE88_POW3; + v += charCodeToInt[input[i+4]] * BASE88_POW4; + uint32arr[j++] = v; + } + if ( m === 0 ) { return; } + v = charCodeToInt[input[n+0]] + + charCodeToInt[input[n+1]] * BASE88_POW1; + if ( m > 2 ) { + v += charCodeToInt[input[n+2]] * BASE88_POW2; + if ( m > 3 ) { + v += charCodeToInt[input[n+3]] * BASE88_POW3; + } + } + const uint8arr = new Uint8Array(arrbuf, j << 2); + uint8arr[0] = v & 255; + if ( v !== 0 ) { + v >>>= 8; + uint8arr[1] = v & 255; + if ( v !== 0 ) { + v >>>= 8; + uint8arr[2] = v & 255; + } + } +}; + +const sparseArrayBufferToStr = (arrbuf, details) => { + const end = details.end; + const uint8out = new Uint8Array(details.sparseSize); + const uint32len = end >>> 2; + const uint32arr = new Uint32Array(arrbuf, 0, uint32len); + let j = 0, n = 0, r = 0; + for ( let i = 0; i < uint32len; i++ ) { + n = uint32arr[i]; + if ( n !== 0 ) { + for (;;) { + r = n % NUMSAFECHARS; + uint8out[j++] = intToCharCode[r]; + n -= r; + if ( n === 0 ) { break; } + n /= NUMSAFECHARS; + } + } + uint8out[j++] = SEPARATORCHARCODE; + } + const uint8rem = end & 0b11; + if ( uint8rem !== 0 ) { + uint8out[j++] = SENTINELCHARCODE; + const uint8arr = new Uint8Array(arrbuf, end - uint8rem, uint8rem); + for ( let i = 0; i < uint8rem; i++ ) { + n = uint8arr[i]; + if ( n !== 0 ) { + for (;;) { + r = n % NUMSAFECHARS; + uint8out[j++] = intToCharCode[r]; + n -= r; + if ( n === 0 ) { break; } + n /= NUMSAFECHARS; + } + } + uint8out[j++] = SEPARATORCHARCODE; + } + } + return textDecoder.decode(uint8out); +}; + +const sparseArrayBufferFromStr = (sparseStr, arrbuf) => { + const sparseLen = sparseStr.length; + const input = uint8InputFromAsciiStr(sparseStr); + const end = arrbuf.byteLength; + const uint32len = end >>> 2; + const uint32arr = new Uint32Array(arrbuf, 0, uint32len); + let i = 0, j = 0, c = 0, n = 0, m = 0; + for ( ; j < sparseLen; i++ ) { + c = input[j++]; + if ( c === SEPARATORCHARCODE ) { continue; } + if ( c === SENTINELCHARCODE ) { break; } + n = charCodeToInt[c]; + m = 1; + for (;;) { + c = input[j++]; + if ( c === SEPARATORCHARCODE ) { break; } + m *= NUMSAFECHARS; + n += m * charCodeToInt[c]; + } + uint32arr[i] = n; + } + if ( c === SENTINELCHARCODE ) { + i <<= 2; + const uint8arr = new Uint8Array(arrbuf, i); + for ( ; j < sparseLen; i++ ) { + c = input[j++]; + if ( c === SEPARATORCHARCODE ) { continue; } + n = charCodeToInt[c]; + m = 1; + for (;;) { + c = input[j++]; + if ( c === SEPARATORCHARCODE ) { break; } + m *= NUMSAFECHARS; + n += m * charCodeToInt[c]; + } + uint8arr[i] = n; + } + } +}; + +/******************************************************************************/ + +const _serialize = data => { + // Primitive types + if ( data === 0 ) { + writeBuffer.push(C_ZERO); + return; + } + if ( data === null ) { + writeBuffer.push(C_NULL); + return; + } + if ( data === undefined ) { + writeBuffer.push(C_UNDEFINED); + return; + } + // Type name + switch ( typeToSerializedInt[typeof data] ) { + case I_STRING: { + const length = data.length; + if ( length < NUMSAFECHARS ) { + writeBuffer.push(C_STRING_SMALL + intToChar[length], data); + } else { + writeBuffer.push(C_STRING_LARGE + strFromLargeUint(length), data); + } + return; + } + case I_NUMBER: + if ( isInteger(data) ) { + if ( data >= NUMSAFECHARS ) { + writeBuffer.push(C_INTEGER_LARGE_POS + strFromLargeUint(data)); + } else if ( data > 0 ) { + writeBuffer.push(C_INTEGER_SMALL_POS + intToChar[data]); + } else if ( data > -NUMSAFECHARS ) { + writeBuffer.push(C_INTEGER_SMALL_NEG + intToChar[-data]); + } else { + writeBuffer.push(C_INTEGER_LARGE_NEG + strFromLargeUint(-data)); + } + } else { + const s = `${data}`; + writeBuffer.push(C_FLOAT + strFromLargeUint(s.length) + s); + } + return; + case I_BOOL: + writeBuffer.push(data ? C_BOOL_TRUE : C_BOOL_FALSE); + return; + case I_OBJECT: + break; + default: + return; + } + const xtypeName = Object.prototype.toString.call(data); + const xtypeInt = xtypeToSerializedInt[xtypeName]; + if ( xtypeInt === I_REGEXP ) { + writeBuffer.push(C_REGEXP); + _serialize(data.source); + _serialize(data.flags); + return; + } + if ( xtypeInt === I_DATE ) { + writeBuffer.push(C_DATE + _serialize(data.getTime())); + return; + } + // Reference to composite types + const ref = writeRefs.get(data); + if ( ref !== undefined ) { + writeBuffer.push(C_REFERENCE + strFromLargeUint(ref)); + return; + } + // Remember reference + writeRefs.set(data, refCounter++); + // Extended type name + switch ( xtypeInt ) { + case I_ARRAY: { + const size = data.length; + if ( size < NUMSAFECHARS ) { + writeBuffer.push(C_ARRAY_SMALL + intToChar[size]); + } else { + writeBuffer.push(C_ARRAY_LARGE + strFromLargeUint(size)); + } + for ( const v of data ) { + _serialize(v); + } + return; + } + case I_SET: { + const size = data.size; + if ( size < NUMSAFECHARS ) { + writeBuffer.push(C_SET_SMALL + intToChar[size]); + } else { + writeBuffer.push(C_SET_LARGE + strFromLargeUint(size)); + } + for ( const v of data ) { + _serialize(v); + } + return; + } + case I_MAP: { + const size = data.size; + if ( size < NUMSAFECHARS ) { + writeBuffer.push(C_MAP_SMALL + intToChar[size]); + } else { + writeBuffer.push(C_MAP_LARGE + strFromLargeUint(size)); + } + for ( const [ k, v ] of data ) { + _serialize(k); + _serialize(v); + } + return; + } + case I_ARRAYBUFFER: { + const byteLength = data.byteLength; + writeBuffer.push(C_ARRAYBUFFER + strFromLargeUint(byteLength)); + _serialize(data.maxByteLength); + const arrbuffDetails = analyzeArrayBuffer(data); + _serialize(arrbuffDetails.dense); + const str = arrbuffDetails.dense + ? denseArrayBufferToStr(data, arrbuffDetails) + : sparseArrayBufferToStr(data, arrbuffDetails); + _serialize(str); + //console.log(`arrbuf size=${byteLength} content size=${arrbuffDetails.end} dense=${arrbuffDetails.dense} array size=${arrbuffDetails.dense ? arrbuffDetails.denseSize : arrbuffDetails.sparseSize} serialized size=${str.length}`); + return; + } + case I_INT8ARRAY: + case I_UINT8ARRAY: + case I_UINT8CLAMPEDARRAY: + case I_INT16ARRAY: + case I_UINT16ARRAY: + case I_INT32ARRAY: + case I_UINT32ARRAY: + case I_FLOAT32ARRAY: + case I_FLOAT64ARRAY: + writeBuffer.push( + typeToSerializedChar[xtypeName], + strFromLargeUint(data.byteOffset), + strFromLargeUint(data.length) + ); + _serialize(data.buffer); + return; + case I_DATAVIEW: + writeBuffer.push(C_DATAVIEW, strFromLargeUint(data.byteOffset), strFromLargeUint(data.byteLength)); + _serialize(data.buffer); + return; + default: { + const keys = Object.keys(data); + const size = keys.length; + if ( size < NUMSAFECHARS ) { + writeBuffer.push(C_SMALL_OBJECT + intToChar[size]); + } else { + writeBuffer.push(C_LARGE_OBJECT + strFromLargeUint(size)); + } + for ( const key of keys ) { + _serialize(key); + _serialize(data[key]); + } + break; + } + } +}; + +/******************************************************************************/ + +const _deserialize = ( ) => { + if ( readPtr >= readEnd ) { return; } + const type = charCodeToInt[readStr.charCodeAt(readPtr++)]; + switch ( type ) { + // Primitive types + case I_STRING_SMALL: + case I_STRING_LARGE: { + const size = type === I_STRING_SMALL + ? charCodeToInt[readStr.charCodeAt(readPtr++)] + : deserializeLargeUint(); + const beg = readPtr; + readPtr += size; + return readStr.slice(beg, readPtr); + } + case I_ZERO: + return 0; + case I_INTEGER_SMALL_POS: + return charCodeToInt[readStr.charCodeAt(readPtr++)]; + case I_INTEGER_SMALL_NEG: + return -charCodeToInt[readStr.charCodeAt(readPtr++)]; + case I_INTEGER_LARGE_POS: + return deserializeLargeUint(); + case I_INTEGER_LARGE_NEG: + return -deserializeLargeUint(); + case I_BOOL_FALSE: + return false; + case I_BOOL_TRUE: + return true; + case I_NULL: + return null; + case I_UNDEFINED: + return; + case I_FLOAT: { + const size = deserializeLargeUint(); + const beg = readPtr; + readPtr += size; + return parseFloat(readStr.slice(beg, readPtr)); + } + case I_REGEXP: { + const source = _deserialize(); + const flags = _deserialize(); + return new RegExp(source, flags); + } + case I_DATE: { + const time = _deserialize(); + return new Date(time); + } + case I_REFERENCE: { + const ref = deserializeLargeUint(); + return readRefs.get(ref); + } + case I_SMALL_OBJECT: + case I_LARGE_OBJECT: { + const entries = []; + const size = type === I_SMALL_OBJECT + ? charCodeToInt[readStr.charCodeAt(readPtr++)] + : deserializeLargeUint(); + for ( let i = 0; i < size; i++ ) { + const k = _deserialize(); + const v = _deserialize(); + entries.push([ k, v ]); + } + const out = Object.fromEntries(entries); + readRefs.set(refCounter++, out); + return out; + } + case I_ARRAY_SMALL: + case I_ARRAY_LARGE: { + const out = []; + const size = type === I_ARRAY_SMALL + ? charCodeToInt[readStr.charCodeAt(readPtr++)] + : deserializeLargeUint(); + for ( let i = 0; i < size; i++ ) { + out.push(_deserialize()); + } + readRefs.set(refCounter++, out); + return out; + } + case I_SET_SMALL: + case I_SET_LARGE: { + const entries = []; + const size = type === I_SET_SMALL + ? charCodeToInt[readStr.charCodeAt(readPtr++)] + : deserializeLargeUint(); + for ( let i = 0; i < size; i++ ) { + entries.push(_deserialize()); + } + const out = new Set(entries); + readRefs.set(refCounter++, out); + return out; + } + case I_MAP_SMALL: + case I_MAP_LARGE: { + const entries = []; + const size = type === I_MAP_SMALL + ? charCodeToInt[readStr.charCodeAt(readPtr++)] + : deserializeLargeUint(); + for ( let i = 0; i < size; i++ ) { + const k = _deserialize(); + const v = _deserialize(); + entries.push([ k, v ]); + } + const out = new Map(entries); + readRefs.set(refCounter++, out); + return out; + } + case I_ARRAYBUFFER: { + const byteLength = deserializeLargeUint(); + const maxByteLength = _deserialize(); + let options; + if ( maxByteLength !== 0 && maxByteLength !== byteLength ) { + options = { maxByteLength }; + } + const arrbuf = new ArrayBuffer(byteLength, options); + const dense = _deserialize(); + const str = _deserialize(); + if ( dense ) { + denseArrayBufferFromStr(str, arrbuf); + } else { + sparseArrayBufferFromStr(str, arrbuf); + } + readRefs.set(refCounter++, arrbuf); + return arrbuf; + } + case I_INT8ARRAY: + case I_UINT8ARRAY: + case I_UINT8CLAMPEDARRAY: + case I_INT16ARRAY: + case I_UINT16ARRAY: + case I_INT32ARRAY: + case I_UINT32ARRAY: + case I_FLOAT32ARRAY: + case I_FLOAT64ARRAY: + case I_DATAVIEW: { + const byteOffset = deserializeLargeUint(); + const length = deserializeLargeUint(); + const arrayBuffer = _deserialize(); + const ctor = toArrayBufferViewConstructor[`${type}`]; + const out = new ctor(arrayBuffer, byteOffset, length); + readRefs.set(refCounter++, out); + return out; + } + default: + break; + } + readPtr = FAILMARK; +}; + +/******************************************************************************* + * + * LZ4 block compression/decompression + * + * Imported from: + * https://github.com/gorhill/lz4-wasm/blob/8995cdef7b/dist/lz4-block-codec-js.js + * + * Customized to avoid external dependencies as I entertain the idea of + * spinning off the serializer as a standalone utility for all to use. + * + * */ + +class LZ4BlockJS { + constructor() { + this.hashTable = undefined; + this.outputBuffer = undefined; + } + reset() { + this.hashTable = undefined; + this.outputBuffer = undefined; + } + growOutputBuffer(size) { + if ( this.outputBuffer !== undefined ) { + if ( this.outputBuffer.byteLength >= size ) { return; } + } + this.outputBuffer = new ArrayBuffer(size + 0xFFFF & 0x7FFF0000); + } + encodeBound(size) { + return size > 0x7E000000 ? 0 : size + (size / 255 | 0) + 16; + } + encodeBlock(iBuf, oOffset) { + const iLen = iBuf.byteLength; + if ( iLen >= 0x7E000000 ) { throw new RangeError(); } + // "The last match must start at least 12 bytes before end of block" + const lastMatchPos = iLen - 12; + // "The last 5 bytes are always literals" + const lastLiteralPos = iLen - 5; + if ( this.hashTable === undefined ) { + this.hashTable = new Int32Array(65536); + } + this.hashTable.fill(-65536); + if ( isInstanceOf(iBuf, 'ArrayBuffer') ) { + iBuf = new Uint8Array(iBuf); + } + const oLen = oOffset + this.encodeBound(iLen); + this.growOutputBuffer(oLen); + const oBuf = new Uint8Array(this.outputBuffer, 0, oLen); + let iPos = 0; + let oPos = oOffset; + let anchorPos = 0; + // sequence-finding loop + for (;;) { + let refPos; + let mOffset; + let sequence = iBuf[iPos] << 8 | iBuf[iPos+1] << 16 | iBuf[iPos+2] << 24; + // match-finding loop + while ( iPos <= lastMatchPos ) { + sequence = sequence >>> 8 | iBuf[iPos+3] << 24; + const hash = (sequence * 0x9E37 & 0xFFFF) + (sequence * 0x79B1 >>> 16) & 0xFFFF; + refPos = this.hashTable[hash]; + this.hashTable[hash] = iPos; + mOffset = iPos - refPos; + if ( + mOffset < 65536 && + iBuf[refPos+0] === ((sequence ) & 0xFF) && + iBuf[refPos+1] === ((sequence >>> 8) & 0xFF) && + iBuf[refPos+2] === ((sequence >>> 16) & 0xFF) && + iBuf[refPos+3] === ((sequence >>> 24) & 0xFF) + ) { + break; + } + iPos += 1; + } + // no match found + if ( iPos > lastMatchPos ) { break; } + // match found + let lLen = iPos - anchorPos; + let mLen = iPos; + iPos += 4; refPos += 4; + while ( iPos < lastLiteralPos && iBuf[iPos] === iBuf[refPos] ) { + iPos += 1; refPos += 1; + } + mLen = iPos - mLen; + const token = mLen < 19 ? mLen - 4 : 15; + // write token, length of literals if needed + if ( lLen >= 15 ) { + oBuf[oPos++] = 0xF0 | token; + let l = lLen - 15; + while ( l >= 255 ) { + oBuf[oPos++] = 255; + l -= 255; + } + oBuf[oPos++] = l; + } else { + oBuf[oPos++] = (lLen << 4) | token; + } + // write literals + while ( lLen-- ) { + oBuf[oPos++] = iBuf[anchorPos++]; + } + if ( mLen === 0 ) { break; } + // write offset of match + oBuf[oPos+0] = mOffset; + oBuf[oPos+1] = mOffset >>> 8; + oPos += 2; + // write length of match if needed + if ( mLen >= 19 ) { + let l = mLen - 19; + while ( l >= 255 ) { + oBuf[oPos++] = 255; + l -= 255; + } + oBuf[oPos++] = l; + } + anchorPos = iPos; + } + // last sequence is literals only + let lLen = iLen - anchorPos; + if ( lLen >= 15 ) { + oBuf[oPos++] = 0xF0; + let l = lLen - 15; + while ( l >= 255 ) { + oBuf[oPos++] = 255; + l -= 255; + } + oBuf[oPos++] = l; + } else { + oBuf[oPos++] = lLen << 4; + } + while ( lLen-- ) { + oBuf[oPos++] = iBuf[anchorPos++]; + } + return new Uint8Array(oBuf.buffer, 0, oPos); + } + decodeBlock(iBuf, iOffset, oLen) { + const iLen = iBuf.byteLength; + this.growOutputBuffer(oLen); + const oBuf = new Uint8Array(this.outputBuffer, 0, oLen); + let iPos = iOffset, oPos = 0; + while ( iPos < iLen ) { + const token = iBuf[iPos++]; + // literals + let clen = token >>> 4; + // length of literals + if ( clen !== 0 ) { + if ( clen === 15 ) { + let l; + for (;;) { + l = iBuf[iPos++]; + if ( l !== 255 ) { break; } + clen += 255; + } + clen += l; + } + // copy literals + const end = iPos + clen; + while ( iPos < end ) { + oBuf[oPos++] = iBuf[iPos++]; + } + if ( iPos === iLen ) { break; } + } + // match + const mOffset = iBuf[iPos+0] | (iBuf[iPos+1] << 8); + if ( mOffset === 0 || mOffset > oPos ) { return; } + iPos += 2; + // length of match + clen = (token & 0x0F) + 4; + if ( clen === 19 ) { + let l; + for (;;) { + l = iBuf[iPos++]; + if ( l !== 255 ) { break; } + clen += 255; + } + clen += l; + } + // copy match + const end = oPos + clen; + let mPos = oPos - mOffset; + while ( oPos < end ) { + oBuf[oPos++] = oBuf[mPos++]; + } + } + return oBuf; + } + encode(input, outputOffset) { + if ( isInstanceOf(input, 'ArrayBuffer') ) { + input = new Uint8Array(input); + } else if ( isInstanceOf(input, 'Uint8Array') === false ) { + throw new TypeError(); + } + return this.encodeBlock(input, outputOffset); + } + decode(input, inputOffset, outputSize) { + if ( isInstanceOf(input, 'ArrayBuffer') ) { + input = new Uint8Array(input); + } else if ( isInstanceOf(input, 'Uint8Array') === false ) { + throw new TypeError(); + } + return this.decodeBlock(input, inputOffset, outputSize); + } +} + +/******************************************************************************* + * + * Synchronous APIs + * + * */ + +export const serialize = (data, options = {}) => { + refCounter = 1; + _serialize(data); + writeBuffer.unshift(MAGICPREFIX); + const s = writeBuffer.join(''); + writeRefs.clear(); + writeBuffer.length = 0; + if ( options.compress !== true ) { return s; } + const lz4Util = new LZ4BlockJS(); + const encoder = new TextEncoder(); + const uint8ArrayBefore = encoder.encode(s); + const uint8ArrayAfter = lz4Util.encode(uint8ArrayBefore, 0); + const lz4 = { + size: uint8ArrayBefore.length, + data: new Uint8Array(uint8ArrayAfter), + }; + refCounter = 1; + _serialize(lz4); + writeBuffer.unshift(MAGICLZ4PREFIX); + const t = writeBuffer.join(''); + writeRefs.clear(); + writeBuffer.length = 0; + const ratio = t.length / s.length; + return ratio <= 0.85 ? t : s; +}; + +export const deserialize = s => { + if ( s.startsWith(MAGICLZ4PREFIX) ) { + refCounter = 1; + readStr = s; + readEnd = s.length; + readPtr = MAGICLZ4PREFIX.length; + const lz4 = _deserialize(); + readRefs.clear(); + readStr = ''; + const lz4Util = new LZ4BlockJS(); + const uint8ArrayAfter = lz4Util.decode(lz4.data, 0, lz4.size); + s = textDecoder.decode(new Uint8Array(uint8ArrayAfter)); + } + if ( s.startsWith(MAGICPREFIX) === false ) { return; } + refCounter = 1; + readStr = s; + readEnd = s.length; + readPtr = MAGICPREFIX.length; + const data = _deserialize(); + readRefs.clear(); + readStr = ''; + uint8Input = null; + if ( readPtr === FAILMARK ) { return; } + return data; +}; + +export const canDeserialize = s => + typeof s === 'string' && + (s.startsWith(MAGICLZ4PREFIX) || s.startsWith(MAGICPREFIX)); + +/******************************************************************************* + * + * Configuration + * + * */ + +const defaultConfig = { + threadTTL: 5000, +}; + +const validateConfig = { + threadTTL: val => val > 0, +}; + +const currentConfig = Object.assign({}, defaultConfig); + +export const getConfig = ( ) => Object.assign({}, currentConfig); + +export const setConfig = config => { + for ( const key in Object.keys(config) ) { + if ( defaultConfig.hasOwnProperty(key) === false ) { continue; } + const val = config[key]; + if ( typeof val !== typeof defaultConfig[key] ) { continue; } + if ( (validateConfig[key])(val) === false ) { continue; } + currentConfig[key] = val; + } +}; + +/******************************************************************************* + * + * Asynchronous APIs + * + * Being asynchronous allows to support workers and future features such as + * checksums. + * + * */ + +class Thread { + constructor(gcer) { + this.jobs = new Map(); + this.jobIdGenerator = 1; + this.workerAccessTime = 0; + this.workerTimer = undefined; + this.gcer = gcer; + this.workerPromise = new Promise(resolve => { + let worker = null; + try { + worker = new Worker('js/scuo-serializer.js', { type: 'module' }); + worker.onmessage = ev => { + const msg = ev.data; + if ( isInstanceOf(msg, 'Object') === false ) { return; } + if ( msg.what === 'ready!' ) { + worker.onmessage = ev => { this.onmessage(ev); }; + worker.onerror = null; + resolve(worker); + } + }; + worker.onerror = ( ) => { + worker.onmessage = worker.onerror = null; + resolve(null); + }; + worker.postMessage({ what: 'ready?', config: currentConfig }); + } catch(ex) { + console.info(ex); + worker.onmessage = worker.onerror = null; + resolve(null); + } + }); + } + + countdownWorker() { + if ( this.workerTimer !== undefined ) { return; } + this.workerTimer = setTimeout(async ( ) => { + this.workerTimer = undefined; + if ( this.jobs.size !== 0 ) { return; } + const idleTime = Date.now() - this.workerAccessTime; + if ( idleTime < currentConfig.threadTTL ) { + return this.countdownWorker(); + } + const worker = await this.workerPromise; + if ( this.jobs.size !== 0 ) { return; } + this.gcer(this); + if ( worker === null ) { return; } + worker.onmessage = worker.onerror = null; + worker.terminate(); + }, currentConfig.threadTTL); + } + + onmessage(ev) { + const job = ev.data; + const resolve = this.jobs.get(job.id); + if ( resolve === undefined ) { return; } + this.jobs.delete(job.id); + resolve(job.result); + if ( this.jobs.size !== 0 ) { return; } + this.countdownWorker(); + } + + async serialize(data, options) { + this.workerAccessTime = Date.now(); + const worker = await this.workerPromise; + if ( worker === null ) { + const result = serialize(data, options); + this.countdownWorker(); + return result; + } + const id = this.jobIdGenerator++; + return new Promise(resolve => { + const job = { what: 'serialize', id, data, options }; + this.jobs.set(job.id, resolve); + worker.postMessage(job); + }); + } + + async deserialize(data, options) { + this.workerAccessTime = Date.now(); + const worker = await this.workerPromise; + if ( worker === null ) { + const result = deserialize(data, options); + this.countdownWorker(); + return result; + } + const id = this.jobIdGenerator++; + return new Promise(resolve => { + const job = { what: 'deserialize', id, data, options }; + this.jobs.set(job.id, resolve); + worker.postMessage(job); + }); + } +} + +const threads = { + pool: [], + thread(maxPoolSize) { + for ( const thread of this.pool ) { + if ( thread.jobs.size === 0 ) { return thread; } + } + const len = this.pool.length; + if ( len !== 0 && len >= maxPoolSize ) { + if ( len === 1 ) { return this.pool[0]; } + return this.pool.reduce((best, candidate) => + candidate.jobs.size < best.jobs.size ? candidate : best + ); + } + const thread = new Thread(thread => { + const pos = this.pool.indexOf(thread); + if ( pos === -1 ) { return; } + this.pool.splice(pos, 1); + }); + this.pool.push(thread); + return thread; + }, +}; + +export async function serializeAsync(data, options = {}) { + const maxThreadCount = options.multithreaded || 0; + if ( maxThreadCount === 0 ) { + return serialize(data, options); + } + const result = await threads + .thread(maxThreadCount) + .serialize(data, options); + if ( result !== undefined ) { return result; } + return serialize(data, options); +} + +export async function deserializeAsync(data, options = {}) { + const maxThreadCount = options.multithreaded || 0; + if ( maxThreadCount === 0 ) { + return deserialize(data, options); + } + const result = await threads + .thread(maxThreadCount) + .deserialize(data, options); + if ( result !== undefined ) { return result; } + return deserialize(data, options); +} + +/******************************************************************************* + * + * Worker-only code + * + * */ + +if ( isInstanceOf(globalThis, 'DedicatedWorkerGlobalScope') ) { + globalThis.onmessage = ev => { + const msg = ev.data; + switch ( msg.what ) { + case 'ready?': + setConfig(msg.config); + globalThis.postMessage({ what: 'ready!' }); + break; + case 'serialize': + case 'deserialize': { + const result = msg.what === 'serialize' + ? serialize(msg.data, msg.options) + : deserialize(msg.data); + globalThis.postMessage({ id: msg.id, result }); + break; + } + } + }; +} + +/******************************************************************************/ diff --git a/src/js/start.js b/src/js/start.js index 5762619b6d2a4..877d909c4d92d 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -139,7 +139,7 @@ const initializeTabs = async ( ) => { // https://www.reddit.com/r/uBlockOrigin/comments/s7c9go/ // Abort suspending network requests when uBO is merely being installed. -const onVersionReady = lastVersion => { +const onVersionReady = async lastVersion => { if ( lastVersion === vAPI.app.version ) { return; } vAPI.storage.set({ @@ -155,6 +155,11 @@ const onVersionReady = lastVersion => { return; } + // Migrate cache storage + if ( lastVersionInt < vAPI.app.intFromVersion('1.56.1b1') ) { + await cacheStorage.migrate(µb.hiddenSettings.cacheStorageAPI); + } + // Since built-in resources may have changed since last version, we // force a reload of all resources. redirectEngine.invalidateResourcesSelfie(io); @@ -252,19 +257,19 @@ const onUserSettingsReady = fetched => { // Wait for removal of invalid cached data to be completed. const onCacheSettingsReady = async (fetched = {}) => { + let selfieIsInvalid = false; if ( fetched.compiledMagic !== µb.systemSettings.compiledMagic ) { µb.compiledFormatChanged = true; - µb.selfieIsInvalid = true; + selfieIsInvalid = true; ubolog(`Serialized format of static filter lists changed`); } if ( fetched.selfieMagic !== µb.systemSettings.selfieMagic ) { - µb.selfieIsInvalid = true; + selfieIsInvalid = true; ubolog(`Serialized format of selfie changed`); } - if ( µb.selfieIsInvalid ) { - µb.selfieManager.destroy(); - cacheStorage.set(µb.systemSettings); - } + if ( selfieIsInvalid === false ) { return; } + µb.selfieManager.destroy({ janitor: true }); + cacheStorage.set(µb.systemSettings); }; /******************************************************************************/ @@ -305,10 +310,7 @@ const onHiddenSettingsReady = async ( ) => { } // Maybe override default cache storage - µb.supportStats.cacheBackend = await cacheStorage.select( - µb.hiddenSettings.cacheStorageAPI - ); - ubolog(`Backend storage for cache will be ${µb.supportStats.cacheBackend}`); + µb.supportStats.cacheBackend = 'browser.storage.local'; }; /******************************************************************************/ @@ -333,7 +335,6 @@ const onFirstFetchReady = (fetched, adminExtra) => { sessionSwitches.assign(permanentSwitches); onNetWhitelistReady(fetched.netWhitelist, adminExtra); - onVersionReady(fetched.version); }; /******************************************************************************/ @@ -389,23 +390,20 @@ try { const adminExtra = await vAPI.adminStorage.get('toAdd'); ubolog(`Extra admin settings ready ${Date.now()-vAPI.T0} ms after launch`); - // https://github.com/uBlockOrigin/uBlock-issues/issues/1365 - // Wait for onCacheSettingsReady() to be fully ready. - const [ , , lastVersion ] = await Promise.all([ + const lastVersion = await vAPI.storage.get(createDefaultProps()).then(async fetched => { + ubolog(`Version ready ${Date.now()-vAPI.T0} ms after launch`); + await onVersionReady(fetched.version); + return fetched; + }).then(fetched => { + ubolog(`First fetch ready ${Date.now()-vAPI.T0} ms after launch`); + onFirstFetchReady(fetched, adminExtra); + return fetched.version; + }); + + await Promise.all([ µb.loadSelectedFilterLists().then(( ) => { ubolog(`List selection ready ${Date.now()-vAPI.T0} ms after launch`); }), - cacheStorage.get( - { compiledMagic: 0, selfieMagic: 0 } - ).then(fetched => { - ubolog(`Cache magic numbers ready ${Date.now()-vAPI.T0} ms after launch`); - onCacheSettingsReady(fetched); - }), - vAPI.storage.get(createDefaultProps()).then(fetched => { - ubolog(`First fetch ready ${Date.now()-vAPI.T0} ms after launch`); - onFirstFetchReady(fetched, adminExtra); - return fetched.version; - }), µb.loadUserSettings().then(fetched => { ubolog(`User settings ready ${Date.now()-vAPI.T0} ms after launch`); onUserSettingsReady(fetched); @@ -413,6 +411,10 @@ try { µb.loadPublicSuffixList().then(( ) => { ubolog(`PSL ready ${Date.now()-vAPI.T0} ms after launch`); }), + cacheStorage.get({ compiledMagic: 0, selfieMagic: 0 }).then(bin => { + ubolog(`Cache magic numbers ready ${Date.now()-vAPI.T0} ms after launch`); + onCacheSettingsReady(bin); + }), ]); // https://github.com/uBlockOrigin/uBlock-issues/issues/1547 diff --git a/src/js/static-ext-filtering-db.js b/src/js/static-ext-filtering-db.js index 64a9c8df0177e..e669c1e11a3ce 100644 --- a/src/js/static-ext-filtering-db.js +++ b/src/js/static-ext-filtering-db.js @@ -141,8 +141,8 @@ const StaticExtFilteringHostnameDB = class { toSelfie() { return { version: this.version, - hostnameToSlotIdMap: Array.from(this.hostnameToSlotIdMap), - regexToSlotIdMap: Array.from(this.regexToSlotIdMap), + hostnameToSlotIdMap: this.hostnameToSlotIdMap, + regexToSlotIdMap: this.regexToSlotIdMap, hostnameSlots: this.hostnameSlots, strSlots: this.strSlots, size: this.size @@ -150,11 +150,11 @@ const StaticExtFilteringHostnameDB = class { } fromSelfie(selfie) { - if ( selfie === undefined ) { return; } - this.hostnameToSlotIdMap = new Map(selfie.hostnameToSlotIdMap); + if ( typeof selfie !== 'object' || selfie === null ) { return; } + this.hostnameToSlotIdMap = selfie.hostnameToSlotIdMap; // Regex-based lookup available in uBO 1.47.0 and above - if ( Array.isArray(selfie.regexToSlotIdMap) ) { - this.regexToSlotIdMap = new Map(selfie.regexToSlotIdMap); + if ( selfie.regexToSlotIdMap ) { + this.regexToSlotIdMap = selfie.regexToSlotIdMap; } this.hostnameSlots = selfie.hostnameSlots; this.strSlots = selfie.strSlots; diff --git a/src/js/static-ext-filtering.js b/src/js/static-ext-filtering.js index 8a2905eb6969c..e616e6350e927 100644 --- a/src/js/static-ext-filtering.js +++ b/src/js/static-ext-filtering.js @@ -26,9 +26,8 @@ import cosmeticFilteringEngine from './cosmetic-filtering.js'; import htmlFilteringEngine from './html-filtering.js'; import httpheaderFilteringEngine from './httpheader-filtering.js'; -import io from './assets.js'; -import logger from './logger.js'; import scriptletFilteringEngine from './scriptlet-filtering.js'; +import logger from './logger.js'; /******************************************************************************* @@ -147,34 +146,24 @@ staticExtFilteringEngine.fromCompiledContent = function(reader, options) { htmlFilteringEngine.fromCompiledContent(reader, options); }; -staticExtFilteringEngine.toSelfie = function(path) { - return io.put( - `${path}/main`, - JSON.stringify({ - cosmetic: cosmeticFilteringEngine.toSelfie(), - scriptlets: scriptletFilteringEngine.toSelfie(), - httpHeaders: httpheaderFilteringEngine.toSelfie(), - html: htmlFilteringEngine.toSelfie(), - }) - ); +staticExtFilteringEngine.toSelfie = function() { + return { + cosmetic: cosmeticFilteringEngine.toSelfie(), + scriptlets: scriptletFilteringEngine.toSelfie(), + httpHeaders: httpheaderFilteringEngine.toSelfie(), + html: htmlFilteringEngine.toSelfie(), + }; }; -staticExtFilteringEngine.fromSelfie = function(path) { - return io.get(`${path}/main`).then(details => { - let selfie; - try { - selfie = JSON.parse(details.content); - } catch (ex) { - } - if ( selfie instanceof Object === false ) { return false; } - cosmeticFilteringEngine.fromSelfie(selfie.cosmetic); - httpheaderFilteringEngine.fromSelfie(selfie.httpHeaders); - htmlFilteringEngine.fromSelfie(selfie.html); - if ( scriptletFilteringEngine.fromSelfie(selfie.scriptlets) === false ) { - return false; - } - return true; - }); +staticExtFilteringEngine.fromSelfie = async function(selfie) { + if ( typeof selfie !== 'object' || selfie === null ) { return false; } + cosmeticFilteringEngine.fromSelfie(selfie.cosmetic); + httpheaderFilteringEngine.fromSelfie(selfie.httpHeaders); + htmlFilteringEngine.fromSelfie(selfie.html); + if ( scriptletFilteringEngine.fromSelfie(selfie.scriptlets) === false ) { + return false; + } + return true; }; /******************************************************************************/ diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index 9189c01bc9604..86d042248c253 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -28,7 +28,6 @@ import { queueTask, dropTask } from './tasks.js'; import BidiTrieContainer from './biditrie.js'; import HNTrieContainer from './hntrie.js'; -import { sparseBase64 } from './base64-custom.js'; import { CompiledListReader } from './static-filtering-io.js'; import * as sfp from './static-filtering-parser.js'; @@ -493,17 +492,13 @@ const filterDataReset = ( ) => { filterData.fill(0); filterDataWritePtr = 2; }; -const filterDataToSelfie = ( ) => { - return JSON.stringify(Array.from(filterData.subarray(0, filterDataWritePtr))); -}; +const filterDataToSelfie = ( ) => + filterData.subarray(0, filterDataWritePtr); + const filterDataFromSelfie = selfie => { - if ( typeof selfie !== 'string' || selfie === '' ) { return false; } - const data = JSON.parse(selfie); - if ( Array.isArray(data) === false ) { return false; } - filterDataGrow(data.length); - filterDataWritePtr = data.length; - filterData.set(data); - filterDataShrink(); + if ( selfie instanceof Int32Array === false ) { return false; } + filterData = selfie; + filterDataWritePtr = selfie.length; return true; }; @@ -519,53 +514,15 @@ const filterRefsReset = ( ) => { filterRefs.fill(null); filterRefsWritePtr = 1; }; -const filterRefsToSelfie = ( ) => { - const refs = []; - for ( let i = 0; i < filterRefsWritePtr; i++ ) { - const v = filterRefs[i]; - if ( v instanceof RegExp ) { - refs.push({ t: 1, s: v.source, f: v.flags }); - continue; - } - if ( Array.isArray(v) ) { - refs.push({ t: 2, v }); - continue; - } - if ( typeof v !== 'object' || v === null ) { - refs.push({ t: 0, v }); - continue; - } - const out = Object.create(null); - for ( const prop of Object.keys(v) ) { - const value = v[prop]; - out[prop] = prop.startsWith('$') - ? (typeof value === 'string' ? '' : null) - : value; - } - refs.push({ t: 3, v: out }); - } - return JSON.stringify(refs); -}; +const filterRefsToSelfie = ( ) => + filterRefs.slice(0, filterRefsWritePtr); + const filterRefsFromSelfie = selfie => { - if ( typeof selfie !== 'string' || selfie === '' ) { return false; } - const refs = JSON.parse(selfie); - if ( Array.isArray(refs) === false ) { return false; } - for ( let i = 0; i < refs.length; i++ ) { - const v = refs[i]; - switch ( v.t ) { - case 0: - case 2: - case 3: - filterRefs[i] = v.v; - break; - case 1: - filterRefs[i] = new RegExp(v.s, v.f); - break; - default: - throw new Error('Unknown filter reference!'); - } + if ( Array.isArray(selfie) === false ) { return false; } + for ( let i = 0, n = selfie.length; i < n; i++ ) { + filterRefs[i] = selfie[i]; } - filterRefsWritePtr = refs.length; + filterRefsWritePtr = selfie.length; return true; }; @@ -3121,14 +3078,11 @@ const urlTokenizer = new (class { } toSelfie() { - return sparseBase64.encode( - this.knownTokens.buffer, - this.knownTokens.byteLength - ); + return this.knownTokens; } fromSelfie(selfie) { - return sparseBase64.decode(selfie, this.knownTokens.buffer); + this.knownTokens = selfie; } // https://github.com/chrisaljoudi/uBlock/issues/1118 @@ -4674,52 +4628,24 @@ FilterContainer.prototype.optimize = function(throttle = 0) { /******************************************************************************/ -FilterContainer.prototype.toSelfie = async function(storage, path) { - if ( typeof storage !== 'object' || storage === null ) { return; } - if ( typeof storage.put !== 'function' ) { return; } - +FilterContainer.prototype.toSelfie = function() { bidiTrieOptimize(true); - keyvalStore.setItem( - 'SNFE.origHNTrieContainer.trieDetails', + keyvalStore.setItem('SNFE.origHNTrieContainer.trieDetails', origHNTrieContainer.optimize() ); - - return Promise.all([ - storage.put( - `${path}/destHNTrieContainer`, - destHNTrieContainer.serialize(sparseBase64) - ), - storage.put( - `${path}/origHNTrieContainer`, - origHNTrieContainer.serialize(sparseBase64) - ), - storage.put( - `${path}/bidiTrie`, - bidiTrie.serialize(sparseBase64) - ), - storage.put( - `${path}/filterData`, - filterDataToSelfie() - ), - storage.put( - `${path}/filterRefs`, - filterRefsToSelfie() - ), - storage.put( - `${path}/main`, - JSON.stringify({ - version: this.selfieVersion, - processedFilterCount: this.processedFilterCount, - acceptedCount: this.acceptedCount, - discardedCount: this.discardedCount, - bitsToBucket: Array.from(this.bitsToBucket).map(kv => { - kv[1] = Array.from(kv[1]); - return kv; - }), - urlTokenizer: urlTokenizer.toSelfie(), - }) - ) - ]); + return { + version: this.selfieVersion, + processedFilterCount: this.processedFilterCount, + acceptedCount: this.acceptedCount, + discardedCount: this.discardedCount, + bitsToBucket: this.bitsToBucket, + urlTokenizer: urlTokenizer.toSelfie(), + destHNTrieContainer: destHNTrieContainer.toSelfie(), + origHNTrieContainer: origHNTrieContainer.toSelfie(), + bidiTrie: bidiTrie.toSelfie(), + filterData: filterDataToSelfie(), + filterRefs: filterRefsToSelfie(), + }; }; FilterContainer.prototype.serialize = async function() { @@ -4735,53 +4661,27 @@ FilterContainer.prototype.serialize = async function() { /******************************************************************************/ -FilterContainer.prototype.fromSelfie = async function(storage, path) { - if ( typeof storage !== 'object' || storage === null ) { return; } - if ( typeof storage.get !== 'function' ) { return; } +FilterContainer.prototype.fromSelfie = async function(selfie) { + if ( typeof selfie !== 'object' || selfie === null ) { return; } this.reset(); this.notReady = true; - const results = await Promise.all([ - storage.get(`${path}/main`), - storage.get(`${path}/destHNTrieContainer`).then(details => - destHNTrieContainer.unserialize(details.content, sparseBase64) - ), - storage.get(`${path}/origHNTrieContainer`).then(details => - origHNTrieContainer.unserialize(details.content, sparseBase64) - ), - storage.get(`${path}/bidiTrie`).then(details => - bidiTrie.unserialize(details.content, sparseBase64) - ), - storage.get(`${path}/filterData`).then(details => - filterDataFromSelfie(details.content) - ), - storage.get(`${path}/filterRefs`).then(details => - filterRefsFromSelfie(details.content) - ), - ]); - + const results = [ + destHNTrieContainer.fromSelfie(selfie.destHNTrieContainer), + origHNTrieContainer.fromSelfie(selfie.origHNTrieContainer), + bidiTrie.fromSelfie(selfie.bidiTrie), + filterDataFromSelfie(selfie.filterData), + filterRefsFromSelfie(selfie.filterRefs), + ]; if ( results.slice(1).every(v => v === true) === false ) { return false; } - const details = results[0]; - if ( typeof details !== 'object' || details === null ) { return false; } - if ( typeof details.content !== 'string' ) { return false; } - if ( details.content === '' ) { return false; } - let selfie; - try { - selfie = JSON.parse(details.content); - } catch (ex) { - } - if ( typeof selfie !== 'object' || selfie === null ) { return false; } if ( selfie.version !== this.selfieVersion ) { return false; } this.processedFilterCount = selfie.processedFilterCount; this.acceptedCount = selfie.acceptedCount; this.discardedCount = selfie.discardedCount; - this.bitsToBucket = new Map(selfie.bitsToBucket.map(kv => { - kv[1] = new Map(kv[1]); - return kv; - })); + this.bitsToBucket = selfie.bitsToBucket; urlTokenizer.fromSelfie(selfie.urlTokenizer); // If this point is never reached, it means the internal state is diff --git a/src/js/storage.js b/src/js/storage.js index 5325a200f92c1..68b52209da820 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -38,7 +38,6 @@ import µb from './background.js'; import { hostnameFromURI } from './uri-utils.js'; import { i18n, i18n$ } from './i18n.js'; import { redirectEngine } from './redirect-engine.js'; -import { sparseBase64 } from './base64-custom.js'; import { ubolog, ubologSet } from './console.js'; import * as sfp from './static-filtering-parser.js'; @@ -974,7 +973,7 @@ onBroadcast(msg => { /******************************************************************************/ µb.getCompiledFilterList = async function(assetKey) { - const compiledPath = 'compiled/' + assetKey; + const compiledPath = `compiled/${assetKey}`; // https://github.com/uBlockOrigin/uBlock-issues/issues/1365 // Verify that the list version matches that of the current compiled @@ -983,11 +982,10 @@ onBroadcast(msg => { this.compiledFormatChanged === false && this.badLists.has(assetKey) === false ) { - const compiledDetails = await io.get(compiledPath); + const content = await io.fromCache(compiledPath); const compilerVersion = `${this.systemSettings.compiledMagic}\n`; - if ( compiledDetails.content.startsWith(compilerVersion) ) { - compiledDetails.assetKey = assetKey; - return compiledDetails; + if ( content.startsWith(compilerVersion) ) { + return { assetKey, content }; } } @@ -1017,7 +1015,7 @@ onBroadcast(msg => { assetKey, trustedSource: this.isTrustedList(assetKey), }); - io.put(compiledPath, compiledContent); + io.toCache(compiledPath, compiledContent); return { assetKey, content: compiledContent }; }; @@ -1046,7 +1044,7 @@ onBroadcast(msg => { /******************************************************************************/ µb.removeCompiledFilterList = function(assetKey) { - io.remove('compiled/' + assetKey); + io.remove(`compiled/${assetKey}`); }; µb.removeFilterList = function(assetKey) { @@ -1173,20 +1171,17 @@ onBroadcast(msg => { const results = await Promise.all(fetchPromises); if ( Array.isArray(results) === false ) { return results; } - let content = ''; + const content = []; for ( let i = 1; i < results.length; i++ ) { const result = results[i]; - if ( - result instanceof Object === false || - typeof result.content !== 'string' || - result.content === '' - ) { - continue; - } - content += '\n\n' + result.content; + if ( result instanceof Object === false ) { continue; } + if ( typeof result.content !== 'string' ) { continue; } + if ( result.content === '' ) { continue; } + content.push(result.content); + } + if ( content.length !== 0 ) { + redirectEngine.resourcesFromString(content.join('\n\n')); } - - redirectEngine.resourcesFromString(content); redirectEngine.selfieFromResources(io); } catch(ex) { ubolog(ex); @@ -1225,8 +1220,8 @@ onBroadcast(msg => { } try { - const result = await io.get(`compiled/${this.pslAssetKey}`); - if ( psl.fromSelfie(result.content, sparseBase64) ) { return; } + const selfie = await io.fromCache(`compiled/${this.pslAssetKey}`); + if ( psl.fromSelfie(selfie) ) { return; } } catch (reason) { ubolog(reason); } @@ -1240,7 +1235,7 @@ onBroadcast(msg => { µb.compilePublicSuffixList = function(content) { const psl = publicSuffixList; psl.parse(content, punycode.toASCII); - io.put(`compiled/${this.pslAssetKey}`, psl.toSelfie(sparseBase64)); + return io.toCache(`compiled/${this.pslAssetKey}`, psl.toSelfie()); }; /******************************************************************************/ @@ -1260,39 +1255,24 @@ onBroadcast(msg => { if ( µb.inMemoryFilters.length !== 0 ) { return; } if ( Object.keys(µb.availableFilterLists).length === 0 ) { return; } await Promise.all([ - io.put( - 'selfie/main', - JSON.stringify({ - magic: µb.systemSettings.selfieMagic, - availableFilterLists: µb.availableFilterLists, - }) - ), - redirectEngine.toSelfie('selfie/redirectEngine'), - staticExtFilteringEngine.toSelfie( - 'selfie/staticExtFilteringEngine' + io.toCache('selfie/main', { + magic: µb.systemSettings.selfieMagic, + availableFilterLists: µb.availableFilterLists, + }), + io.toCache('selfie/staticExtFilteringEngine', + staticExtFilteringEngine.toSelfie() ), - staticNetFilteringEngine.toSelfie(io, - 'selfie/staticNetFilteringEngine' + io.toCache('selfie/staticNetFilteringEngine', + staticNetFilteringEngine.toSelfie() ), ]); lz4Codec.relinquish(); µb.selfieIsInvalid = false; + ubolog(`Selfie was created`); }; const loadMain = async function() { - const details = await io.get('selfie/main'); - if ( - details instanceof Object === false || - typeof details.content !== 'string' || - details.content === '' - ) { - return false; - } - let selfie; - try { - selfie = JSON.parse(details.content); - } catch(ex) { - } + const selfie = await io.fromCache('selfie/main'); if ( selfie instanceof Object === false ) { return false; } if ( selfie.magic !== µb.systemSettings.selfieMagic ) { return false; } if ( selfie.availableFilterLists instanceof Object === false ) { return false; } @@ -1306,12 +1286,11 @@ onBroadcast(msg => { try { const results = await Promise.all([ loadMain(), - redirectEngine.fromSelfie('selfie/redirectEngine'), - staticExtFilteringEngine.fromSelfie( - 'selfie/staticExtFilteringEngine' + io.fromCache('selfie/staticExtFilteringEngine').then(selfie => + staticExtFilteringEngine.fromSelfie(selfie) ), - staticNetFilteringEngine.fromSelfie(io, - 'selfie/staticNetFilteringEngine' + io.fromCache('selfie/staticNetFilteringEngine').then(selfie => + staticNetFilteringEngine.fromSelfie(selfie) ), ]); if ( results.every(v => v) ) { @@ -1325,10 +1304,11 @@ onBroadcast(msg => { return false; }; - const destroy = function() { + const destroy = function(options = {}) { if ( µb.selfieIsInvalid === false ) { - io.remove(/^selfie\//); + io.remove(/^selfie\//, options); µb.selfieIsInvalid = true; + ubolog(`Selfie was removed`); } if ( µb.wakeupReason === 'createSelfie' ) { µb.wakeupReason = ''; @@ -1594,8 +1574,7 @@ onBroadcast(msg => { if ( topic === 'after-asset-updated' ) { // Skip selfie-related content. if ( details.assetKey.startsWith('selfie/') ) { return; } - const cached = typeof details.content === 'string' && - details.content !== ''; + const cached = typeof details.content === 'string' && details.content !== ''; if ( this.availableFilterLists.hasOwnProperty(details.assetKey) ) { if ( cached ) { if ( this.selectedFilterLists.indexOf(details.assetKey) !== -1 ) { @@ -1604,8 +1583,7 @@ onBroadcast(msg => { details.content ); if ( this.badLists.has(details.assetKey) === false ) { - io.put( - 'compiled/' + details.assetKey, + io.toCache(`compiled/${details.assetKey}`, this.compileFilters(details.content, { assetKey: details.assetKey, trustedSource: this.isTrustedList(details.assetKey), diff --git a/src/lib/publicsuffixlist/publicsuffixlist.js b/src/lib/publicsuffixlist/publicsuffixlist.js index 6483c89e2321c..87910d4b0bbdf 100644 --- a/src/lib/publicsuffixlist/publicsuffixlist.js +++ b/src/lib/publicsuffixlist/publicsuffixlist.js @@ -13,8 +13,6 @@ /*! Home: https://github.com/gorhill/publicsuffixlist.js -- GPLv3 APLv2 */ -/* globals WebAssembly, exports:true, module */ - 'use strict'; /******************************************************************************* @@ -70,7 +68,7 @@ const RULES_PTR_SLOT = 100; // 100 / 400 (400-256=144 => 144>128) const SUFFIX_NOT_FOUND_SLOT = 399; // -- / 399 (safe, see above) const CHARDATA_PTR_SLOT = 101; // 101 / 404 const EMPTY_STRING = ''; -const SELFIE_MAGIC = 2; +const SELFIE_MAGIC = 3; let wasmMemory; let pslBuffer32; @@ -499,9 +497,7 @@ const toSelfie = function(encoder) { } return { magic: SELFIE_MAGIC, - buf32: Array.from( - new Uint32Array(pslBuffer8.buffer, 0, pslByteLength >>> 2) - ), + buf32: pslBuffer32.subarray(0, pslByteLength >> 2), }; }; @@ -524,7 +520,7 @@ const fromSelfie = function(selfie, decoder) { } else if ( selfie instanceof Object && selfie.magic === SELFIE_MAGIC && - Array.isArray(selfie.buf32) + selfie.buf32 instanceof Uint32Array ) { byteLength = selfie.buf32.length << 2; allocateBuffers(byteLength); From 385bc21ef133e9cf757f69e6cc1a92b6155332b3 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 26 Feb 2024 17:57:03 -0500 Subject: [PATCH 015/180] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcfc51d56cdb8..293c743a4eaae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Fixes / changes +- [Redesign cache storage](https://github.com/gorhill/uBlock/commit/086766a924) +- [Don't match network filter-derived regexes against non-network URIs](https://github.com/gorhill/uBlock/commit/2262a129ec) +- [Remove obsolete trusted directives](https://github.com/gorhill/uBlock/commit/439a059cca) +- [Support logging details of calls to `json-prune-fetch-response`](https://github.com/gorhill/uBlock/commit/e527a8f9af) - [Escape special whitespace characters in attribute values](https://github.com/gorhill/uBlock/commit/be3e366019) ---------- From 30036ad36ffb59cf22fa6208fc3b2e8cc9acc493 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 26 Feb 2024 18:02:38 -0500 Subject: [PATCH 016/180] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index c1418601688bf..36f894dc2408a 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.56.1.0 +1.56.1.1 From 65ab026f25cb9fa0ceb5a436540470fcf7a895d3 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 26 Feb 2024 18:21:19 -0500 Subject: [PATCH 017/180] Make Firefox dev build auto-update --- dist/firefox/updates.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index ad32bd64e13e0..81eafb1fd278f 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,9 +3,9 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.56.1.0", + "version": "1.56.1.1", "browser_specific_settings": { "gecko": { "strict_min_version": "78.0" } }, - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b0/uBlock0_1.56.1b0.firefox.signed.xpi" + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b1/uBlock0_1.56.1b1.firefox.signed.xpi" } ] } From 4d88b5121cd30aa4fa9d5c2f1dfb449fc7c8807d Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 27 Feb 2024 07:09:02 -0500 Subject: [PATCH 018/180] Fine tune publishing scripts --- dist/chromium/publish-beta.py | 4 ++-- dist/firefox/publish-signed-beta.py | 4 ++-- dist/mv3/firefox/publish-signed-beta.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dist/chromium/publish-beta.py b/dist/chromium/publish-beta.py index 5a6e19fe0b259..a731e9b328c9f 100755 --- a/dist/chromium/publish-beta.py +++ b/dist/chromium/publish-beta.py @@ -43,9 +43,9 @@ github_repo = 'uBlock' # Load/save auth secrets -# The build directory is excluded from git +# The tmp directory is excluded from git ubo_secrets = dict() -ubo_secrets_filename = os.path.join(projdir, 'dist', 'build', 'ubo_secrets') +ubo_secrets_filename = os.path.join(projdir, 'tmp', 'ubo_secrets') if os.path.isfile(ubo_secrets_filename): with open(ubo_secrets_filename) as f: ubo_secrets = json.load(f) diff --git a/dist/firefox/publish-signed-beta.py b/dist/firefox/publish-signed-beta.py index 2e56d12bbe305..ed542ef0073f4 100755 --- a/dist/firefox/publish-signed-beta.py +++ b/dist/firefox/publish-signed-beta.py @@ -69,9 +69,9 @@ github_repo = 'uBlock' # Load/save auth secrets -# The build directory is excluded from git +# The tmp directory is excluded from git ubo_secrets = dict() -ubo_secrets_filename = os.path.join(projdir, 'dist', 'build', 'ubo_secrets') +ubo_secrets_filename = os.path.join(projdir, 'tmp', 'ubo_secrets') if os.path.isfile(ubo_secrets_filename): with open(ubo_secrets_filename) as f: ubo_secrets = json.load(f) diff --git a/dist/mv3/firefox/publish-signed-beta.py b/dist/mv3/firefox/publish-signed-beta.py index 0c5f8cd0f976d..f298cc8334732 100755 --- a/dist/mv3/firefox/publish-signed-beta.py +++ b/dist/mv3/firefox/publish-signed-beta.py @@ -64,9 +64,9 @@ github_repo = 'uBlock' # Load/save auth secrets -# The build directory is excluded from git +# The tmp directory is excluded from git ubo_secrets = dict() -ubo_secrets_filename = os.path.join(projdir, 'dist', 'build', 'ubo_secrets') +ubo_secrets_filename = os.path.join(projdir, 'tmp', 'ubo_secrets') if os.path.isfile(ubo_secrets_filename): with open(ubo_secrets_filename) as f: ubo_secrets = json.load(f) From 7590c0711d35269a5ce8c8c7fda19455c48399ca Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 27 Feb 2024 15:04:05 -0500 Subject: [PATCH 019/180] Fine-tune cache storage related-code Related discussion: https://github.com/uBlockOrigin/uBlock-discussions/discussions/876 Related commit: https://github.com/gorhill/uBlock/commit/086766a924a42affccfd83373a869e888e2ea8a4 --- src/js/background.js | 1 + src/js/cachestorage.js | 84 +++++++++++++----------- src/js/messaging.js | 2 +- src/js/scuo-serializer.js | 135 +++++++++++++++++++++++++++++++------- 4 files changed, 161 insertions(+), 61 deletions(-) diff --git a/src/js/background.js b/src/js/background.js index 80bab5a95168e..123fe5778555d 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -56,6 +56,7 @@ const hiddenSettingsDefault = { blockingProfiles: '11111/#F00 11010/#C0F 11001/#00F 00001', cacheStorageAPI: 'unset', cacheStorageCompression: true, + cacheStorageCompressionThreshold: 65536, cacheStorageMultithread: 2, cacheControlForFirefox1376932: 'no-cache, no-store, must-revalidate', cloudStorageCompression: true, diff --git a/src/js/cachestorage.js b/src/js/cachestorage.js index e70fc322996a0..774ab016b921b 100644 --- a/src/js/cachestorage.js +++ b/src/js/cachestorage.js @@ -58,6 +58,19 @@ const shouldCache = bin => { return out; }; +const missingKeys = (wanted, inbin, outbin) => { + inbin = inbin || {}; + const found = Object.keys(inbin); + Object.assign(outbin, inbin); + if ( found.length === wanted.length ) { return; } + const missing = []; + for ( const key of wanted ) { + if ( outbin.hasOwnProperty(key) ) { continue; } + missing.push(key); + } + return missing; +}; + /******************************************************************************* * * Extension storage @@ -68,11 +81,10 @@ const shouldCache = bin => { const cacheStorage = (( ) => { - const LARGE = 65536; - const compress = async (key, data) => { - const isLarge = typeof data === 'string' && data.length >= LARGE; const µbhs = µb.hiddenSettings; + const isLarge = typeof data === 'string' && + data.length >= µbhs.cacheStorageCompressionThreshold; const after = await scuo.serializeAsync(data, { compress: isLarge && µbhs.cacheStorageCompression, multithreaded: isLarge && µbhs.cacheStorageMultithread || 0, @@ -80,40 +92,39 @@ const cacheStorage = (( ) => { return { key, data: after }; }; - const decompress = async (key, data) => { - if ( scuo.canDeserialize(data) === false ) { - return { key, data }; - } - const isLarge = data.length >= LARGE; - const after = await scuo.deserializeAsync(data, { - multithreaded: isLarge && µb.hiddenSettings.cacheStorageMultithread || 0, + const decompress = async (bin, key) => { + const data = bin[key]; + if ( scuo.isSerialized(data) === false ) { return; } + const µbhs = µb.hiddenSettings; + const isLarge = data.length >= µbhs.cacheStorageCompressionThreshold; + bin[key] = await scuo.deserializeAsync(data, { + multithreaded: isLarge && µbhs.cacheStorageMultithread || 0, }); - return { key, data: after }; }; return { - name: 'browser.storage.local', - - get(arg) { - const keys = arg; - return cacheAPI.get(keysFromGetArg(arg)).then(bin => { - if ( bin !== undefined ) { return bin; } - return extensionStorage.get(keys).catch(reason => { - ubolog(reason); + get(argbin) { + const outbin = {}; + const wanted0 = keysFromGetArg(argbin); + return cacheAPI.get(wanted0).then(bin => { + const wanted1 = missingKeys(wanted0, bin, outbin); + if ( wanted1 === undefined ) { return; } + return extensionStorage.get(wanted1).then(bin => { + const wanted2 = missingKeys(wanted1, bin, outbin); + if ( wanted2 === undefined ) { return; } + if ( argbin instanceof Object === false ) { return; } + if ( Array.isArray(argbin) ) { return; } + for ( const key of wanted2 ) { + if ( argbin.hasOwnProperty(key) === false ) { continue; } + outbin[key] = argbin[key]; + } }); - }).then(bin => { - if ( bin instanceof Object === false ) { return bin; } + }).then(( ) => { const promises = []; - for ( const key of Object.keys(bin) ) { - promises.push(decompress(key, bin[key])); - } - return Promise.all(promises); - }).then(results => { - const bin = {}; - for ( const { key, data } of results ) { - bin[key] = data; + for ( const key of Object.keys(outbin) ) { + promises.push(decompress(outbin, key)); } - return bin; + return Promise.all(promises).then(( ) => outbin); }).catch(reason => { ubolog(reason); }); @@ -183,8 +194,6 @@ const cacheStorage = (( ) => { idbStorage.clear(); return Promise.all(toMigrate); }, - - error: undefined }; })(); @@ -217,6 +226,7 @@ const cacheAPI = (( ) => { } resolve(caches.open(STORAGE_NAME).catch(reason => { ubolog(reason); + return null; })); }); @@ -232,7 +242,7 @@ const cacheAPI = (( ) => { const cache = await cacheStoragePromise; if ( cache === null ) { return; } return cache.match(keyToURL(key)).then(response => { - if ( response instanceof Response === false ) { return; } + if ( response === undefined ) { return; } return response.text(); }).then(text => { if ( text === undefined ) { return; } @@ -302,7 +312,7 @@ const cacheAPI = (( ) => { } const responses = await Promise.all(toFetch); for ( const response of responses ) { - if ( response instanceof Object === false ) { continue; } + if ( response === undefined ) { continue; } const { key, text } = response; if ( typeof key !== 'string' ) { continue; } if ( typeof text !== 'string' ) { continue; } @@ -321,7 +331,7 @@ const cacheAPI = (( ) => { ).catch(( ) => []); }, - async set(keyvalStore) { + set(keyvalStore) { const keys = Object.keys(keyvalStore); if ( keys.length === 0 ) { return; } const promises = []; @@ -331,7 +341,7 @@ const cacheAPI = (( ) => { return Promise.all(promises); }, - async remove(keys) { + remove(keys) { const toRemove = []; if ( typeof keys === 'string' ) { toRemove.push(removeOne(keys)); @@ -343,7 +353,7 @@ const cacheAPI = (( ) => { return Promise.all(toRemove); }, - async clear() { + clear() { return globalThis.caches.delete(STORAGE_NAME).catch(reason => { ubolog(reason); }); diff --git a/src/js/messaging.js b/src/js/messaging.js index 38b03a4093460..468579a4f140b 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -948,7 +948,7 @@ const onMessage = function(request, sender, callback) { case 'cloudPull': request.decode = encoded => { - if ( scuo.canDeserialize(encoded) ) { + if ( scuo.isSerialized(encoded) ) { return scuo.deserializeAsync(encoded, { thread: true }); } // Legacy decoding: needs to be kept around for the foreseeable future. diff --git a/src/js/scuo-serializer.js b/src/js/scuo-serializer.js index 1ffffa6bec600..f5f18fdd4e328 100644 --- a/src/js/scuo-serializer.js +++ b/src/js/scuo-serializer.js @@ -1096,10 +1096,13 @@ export const deserialize = s => { return data; }; -export const canDeserialize = s => +export const isSerialized = s => typeof s === 'string' && (s.startsWith(MAGICLZ4PREFIX) || s.startsWith(MAGICPREFIX)); +export const isCompressed = s => + typeof s === 'string' && s.startsWith(MAGICLZ4PREFIX); + /******************************************************************************* * * Configuration @@ -1137,10 +1140,78 @@ export const setConfig = config => { * * */ +const THREAD_AREYOUREADY = 1; +const THREAD_IAMREADY = 2; +const THREAD_SERIALIZE = 3; +const THREAD_DESERIALIZE = 4; + + +class MainThread { + constructor() { + this.jobs = []; + this.workload = 0; + this.timer = undefined; + this.busy = false; + } + + process() { + if ( this.jobs.length === 0 ) { return; } + const job = this.jobs.shift(); + this.workload -= job.size; + const result = job.what === THREAD_SERIALIZE + ? serialize(job.data, job.options) + : deserialize(job.data); + job.resolve(result); + this.processAsync(); + if ( this.jobs.length === 0 ) { + this.busy = false; + } + } + + processAsync() { + if ( this.timer !== undefined ) { return; } + if ( this.jobs.length === 0 ) { return; } + this.timer = globalThis.requestIdleCallback(deadline => { + this.timer = undefined; + globalThis.queueMicrotask(( ) => { + this.timer = undefined; + this.process(); + }); + this.busy = deadline.timeRemaining() === 0; + }, { timeout: 7 }); + } + + serialize(data, options) { + return new Promise(resolve => { + this.workload += 1; + this.jobs.push({ what: THREAD_SERIALIZE, data, options, size: 1, resolve }); + this.processAsync(); + }); + } + + deserialize(data, options) { + return new Promise(resolve => { + const size = data.length; + this.workload += size; + this.jobs.push({ what: THREAD_DESERIALIZE, data, options, size, resolve }); + this.processAsync(); + }); + } + + get queueSize() { + return this.jobs.length + 1; + } + + get workSize() { + return this.busy ? Number.MAX_SAFE_INTEGER : this.workload * 2; + } +} + class Thread { constructor(gcer) { this.jobs = new Map(); this.jobIdGenerator = 1; + this.workload = 0; this.workerAccessTime = 0; this.workerTimer = undefined; this.gcer = gcer; @@ -1151,7 +1222,7 @@ class Thread { worker.onmessage = ev => { const msg = ev.data; if ( isInstanceOf(msg, 'Object') === false ) { return; } - if ( msg.what === 'ready!' ) { + if ( msg.what === THREAD_IAMREADY ) { worker.onmessage = ev => { this.onmessage(ev); }; worker.onerror = null; resolve(worker); @@ -1161,7 +1232,10 @@ class Thread { worker.onmessage = worker.onerror = null; resolve(null); }; - worker.postMessage({ what: 'ready?', config: currentConfig }); + worker.postMessage({ + what: THREAD_AREYOUREADY, + config: currentConfig, + }); } catch(ex) { console.info(ex); worker.onmessage = worker.onerror = null; @@ -1194,6 +1268,7 @@ class Thread { if ( resolve === undefined ) { return; } this.jobs.delete(job.id); resolve(job.result); + this.workload -= job.size; if ( this.jobs.size !== 0 ) { return; } this.countdownWorker(); } @@ -1208,7 +1283,8 @@ class Thread { } const id = this.jobIdGenerator++; return new Promise(resolve => { - const job = { what: 'serialize', id, data, options }; + this.workload += 1; + const job = { what: THREAD_SERIALIZE, id, data, options, size: 1 }; this.jobs.set(job.id, resolve); worker.postMessage(job); }); @@ -1224,25 +1300,36 @@ class Thread { } const id = this.jobIdGenerator++; return new Promise(resolve => { - const job = { what: 'deserialize', id, data, options }; + const size = data.length; + this.workload += size; + const job = { what: THREAD_DESERIALIZE, id, data, options, size }; this.jobs.set(job.id, resolve); worker.postMessage(job); }); } + + get queueSize() { + return this.jobs.size; + } + + get workSize() { + return this.workload; + } } const threads = { - pool: [], + pool: [ new MainThread() ], thread(maxPoolSize) { - for ( const thread of this.pool ) { - if ( thread.jobs.size === 0 ) { return thread; } - } - const len = this.pool.length; - if ( len !== 0 && len >= maxPoolSize ) { - if ( len === 1 ) { return this.pool[0]; } - return this.pool.reduce((best, candidate) => - candidate.jobs.size < best.jobs.size ? candidate : best - ); + const poolSize = this.pool.length; + if ( poolSize !== 0 && poolSize >= maxPoolSize ) { + if ( poolSize === 1 ) { return this.pool[0]; } + return this.pool.reduce((best, candidate) => { + if ( candidate.queueSize === 0 ) { return candidate; } + if ( best.queueSize === 0 ) { return best; } + return candidate.workSize < best.workSize + ? candidate + : best; + }); } const thread = new Thread(thread => { const pos = this.pool.indexOf(thread); @@ -1267,6 +1354,7 @@ export async function serializeAsync(data, options = {}) { } export async function deserializeAsync(data, options = {}) { + if ( isSerialized(data) === false ) { return data; } const maxThreadCount = options.multithreaded || 0; if ( maxThreadCount === 0 ) { return deserialize(data, options); @@ -1288,16 +1376,17 @@ if ( isInstanceOf(globalThis, 'DedicatedWorkerGlobalScope') ) { globalThis.onmessage = ev => { const msg = ev.data; switch ( msg.what ) { - case 'ready?': + case THREAD_AREYOUREADY: setConfig(msg.config); - globalThis.postMessage({ what: 'ready!' }); + globalThis.postMessage({ what: THREAD_IAMREADY }); + break; + case THREAD_SERIALIZE: + const result = serialize(msg.data, msg.options); + globalThis.postMessage({ id: msg.id, size: msg.size, result }); break; - case 'serialize': - case 'deserialize': { - const result = msg.what === 'serialize' - ? serialize(msg.data, msg.options) - : deserialize(msg.data); - globalThis.postMessage({ id: msg.id, result }); + case THREAD_DESERIALIZE: { + const result = deserialize(msg.data); + globalThis.postMessage({ id: msg.id, size: msg.size, result }); break; } } From ac665ba7ccca01a422c9a992fdae1c942d2c0152 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 27 Feb 2024 15:05:25 -0500 Subject: [PATCH 020/180] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 36f894dc2408a..46df2440537ad 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.56.1.1 +1.56.1.2 From e8b7037ba8b32e7b21e229bb66a2d8299a311859 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 27 Feb 2024 15:11:11 -0500 Subject: [PATCH 021/180] Make Firefox dev build auto-update --- dist/firefox/updates.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index 81eafb1fd278f..5a0564c911972 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,9 +3,9 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.56.1.1", + "version": "1.56.1.2", "browser_specific_settings": { "gecko": { "strict_min_version": "78.0" } }, - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b1/uBlock0_1.56.1b1.firefox.signed.xpi" + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b2/uBlock0_1.56.1b2.firefox.signed.xpi" } ] } From b0fc5d3d21a9bbd9564bd63fdee9388920dd5c3d Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 27 Feb 2024 18:41:10 -0500 Subject: [PATCH 022/180] Fix race condition when saving cache registry Related to new cache storage code. --- src/js/assets.js | 35 ++++++++++++++++++----------------- src/js/cachestorage.js | 4 ++-- src/js/storage.js | 3 ++- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/js/assets.js b/src/js/assets.js index 6484d289d9b8e..77e9b8167b285 100644 --- a/src/js/assets.js +++ b/src/js/assets.js @@ -696,15 +696,14 @@ function getAssetCacheRegistry() { const saveAssetCacheRegistry = (( ) => { const save = ( ) => { timer.off(); - cacheStorage.set({ assetCacheRegistry }); + return cacheStorage.set({ assetCacheRegistry }); }; const timer = vAPI.defer.create(save); - return function(lazily) { - if ( lazily ) { - timer.offon({ sec: 30 }); - } else { - save(); + return (throttle = 0) => { + if ( throttle === 0 ) { + return save(); } + timer.offon({ sec: throttle }); }; })(); @@ -741,7 +740,7 @@ async function assetCacheRead(assetKey, updateReadTime = false) { entry.readTime = Date.now(); if ( updateReadTime ) { - saveAssetCacheRegistry(true); + saveAssetCacheRegistry(23); } return reportBack(bin[internalKey]); @@ -752,18 +751,20 @@ async function assetCacheWrite(assetKey, content, options = {}) { return assetCacheRemove(assetKey); } + const cacheDict = await getAssetCacheRegistry(); + const { resourceTime, url } = options; + const entry = cacheDict[assetKey] || {}; + entry.writeTime = entry.readTime = Date.now(); + entry.resourceTime = resourceTime || 0; + if ( typeof url === 'string' ) { + entry.remoteURL = url; + } + cacheDict[assetKey] = entry; - getAssetCacheRegistry().then(cacheDict => { - const entry = cacheDict[assetKey] || {}; - cacheDict[assetKey] = entry; - entry.writeTime = entry.readTime = Date.now(); - entry.resourceTime = resourceTime || 0; - if ( typeof url === 'string' ) { - entry.remoteURL = url; - } - cacheStorage.set({ assetCacheRegistry, [`cache/${assetKey}`]: content }); - }); + await cacheStorage.set({ [`cache/${assetKey}`]: content }); + + saveAssetCacheRegistry(3); const result = { assetKey, content }; // https://github.com/uBlockOrigin/uBlock-issues/issues/248 diff --git a/src/js/cachestorage.js b/src/js/cachestorage.js index 774ab016b921b..e0fe98705561e 100644 --- a/src/js/cachestorage.js +++ b/src/js/cachestorage.js @@ -87,7 +87,7 @@ const cacheStorage = (( ) => { data.length >= µbhs.cacheStorageCompressionThreshold; const after = await scuo.serializeAsync(data, { compress: isLarge && µbhs.cacheStorageCompression, - multithreaded: isLarge && µbhs.cacheStorageMultithread || 0, + multithreaded: isLarge && µbhs.cacheStorageMultithread || 2, }); return { key, data: after }; }; @@ -98,7 +98,7 @@ const cacheStorage = (( ) => { const µbhs = µb.hiddenSettings; const isLarge = data.length >= µbhs.cacheStorageCompressionThreshold; bin[key] = await scuo.deserializeAsync(data, { - multithreaded: isLarge && µbhs.cacheStorageMultithread || 0, + multithreaded: isLarge && µbhs.cacheStorageMultithread || 2, }); }; diff --git a/src/js/storage.js b/src/js/storage.js index 68b52209da820..99116547e183d 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -1300,6 +1300,7 @@ onBroadcast(msg => { catch (reason) { ubolog(reason); } + ubolog('Selfie not available'); destroy(); return false; }; @@ -1308,7 +1309,7 @@ onBroadcast(msg => { if ( µb.selfieIsInvalid === false ) { io.remove(/^selfie\//, options); µb.selfieIsInvalid = true; - ubolog(`Selfie was removed`); + ubolog('Selfie marked for invalidation'); } if ( µb.wakeupReason === 'createSelfie' ) { µb.wakeupReason = ''; From 96d45f12d27aabb14a86fb5ce549b1a8f36ec891 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 27 Feb 2024 18:42:43 -0500 Subject: [PATCH 023/180] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 46df2440537ad..dd26cb9e3e0d4 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.56.1.2 +1.56.1.3 From a9211cfa2fd94c38ffc1d2c72954b184c280ab3c Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 27 Feb 2024 18:51:05 -0500 Subject: [PATCH 024/180] Make Firefox dev build auto-update --- dist/firefox/updates.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index 5a0564c911972..00c522d26b907 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,9 +3,9 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.56.1.2", + "version": "1.56.1.3", "browser_specific_settings": { "gecko": { "strict_min_version": "78.0" } }, - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b2/uBlock0_1.56.1b2.firefox.signed.xpi" + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b3/uBlock0_1.56.1b3.firefox.signed.xpi" } ] } From 79ea85dbc4e18e4cc22e4db8ad6f8b2504fd3a00 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 27 Feb 2024 21:47:06 -0500 Subject: [PATCH 025/180] More fine tuning of cache storage-related code --- platform/chromium/webext.js | 9 +++ src/js/assets.js | 2 +- src/js/cachestorage.js | 118 +++++++++++++++++++++++++----------- src/js/scuo-serializer.js | 89 ++++++++++++++------------- 4 files changed, 138 insertions(+), 80 deletions(-) diff --git a/platform/chromium/webext.js b/platform/chromium/webext.js index 851b653e26dbd..a0c24c2289ee6 100644 --- a/platform/chromium/webext.js +++ b/platform/chromium/webext.js @@ -156,6 +156,15 @@ if ( chrome.storage.sync instanceof Object ) { }; } +// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/session +webext.storage.session = { + clear: ( ) => Promise.resolve(), + get: ( ) => Promise.resolve(), + getBytesInUse: ( ) => Promise.resolve(), + remove: ( ) => Promise.resolve(), + set: ( ) => Promise.resolve(), +}; + // https://bugs.chromium.org/p/chromium/issues/detail?id=608854 if ( chrome.tabs.removeCSS instanceof Function ) { webext.tabs.removeCSS = promisifyNoFail(chrome.tabs, 'removeCSS'); diff --git a/src/js/assets.js b/src/js/assets.js index 77e9b8167b285..749fb7802aa56 100644 --- a/src/js/assets.js +++ b/src/js/assets.js @@ -838,7 +838,7 @@ async function assetCacheSetDetails(assetKey, details) { } } if ( modified ) { - saveAssetCacheRegistry(); + saveAssetCacheRegistry(3); } } diff --git a/src/js/cachestorage.js b/src/js/cachestorage.js index e0fe98705561e..056cd8b68df1a 100644 --- a/src/js/cachestorage.js +++ b/src/js/cachestorage.js @@ -58,17 +58,19 @@ const shouldCache = bin => { return out; }; -const missingKeys = (wanted, inbin, outbin) => { - inbin = inbin || {}; - const found = Object.keys(inbin); - Object.assign(outbin, inbin); - if ( found.length === wanted.length ) { return; } - const missing = []; - for ( const key of wanted ) { - if ( outbin.hasOwnProperty(key) ) { continue; } - missing.push(key); - } - return missing; +const exGet = (api, wanted, outbin) => { + return api.get(wanted).then(inbin => { + inbin = inbin || {}; + const found = Object.keys(inbin); + Object.assign(outbin, inbin); + if ( found.length === wanted.length ) { return; } + const missing = []; + for ( const key of wanted ) { + if ( outbin.hasOwnProperty(key) ) { continue; } + missing.push(key); + } + return missing; + }); }; /******************************************************************************* @@ -81,15 +83,15 @@ const missingKeys = (wanted, inbin, outbin) => { const cacheStorage = (( ) => { - const compress = async (key, data) => { + const compress = async (bin, key, data) => { const µbhs = µb.hiddenSettings; const isLarge = typeof data === 'string' && data.length >= µbhs.cacheStorageCompressionThreshold; const after = await scuo.serializeAsync(data, { compress: isLarge && µbhs.cacheStorageCompression, - multithreaded: isLarge && µbhs.cacheStorageMultithread || 2, + multithreaded: µbhs.cacheStorageMultithread, }); - return { key, data: after }; + bin[key] = after; }; const decompress = async (bin, key) => { @@ -98,27 +100,24 @@ const cacheStorage = (( ) => { const µbhs = µb.hiddenSettings; const isLarge = data.length >= µbhs.cacheStorageCompressionThreshold; bin[key] = await scuo.deserializeAsync(data, { - multithreaded: isLarge && µbhs.cacheStorageMultithread || 2, + multithreaded: isLarge && µbhs.cacheStorageMultithread || 1, }); }; return { get(argbin) { const outbin = {}; - const wanted0 = keysFromGetArg(argbin); - return cacheAPI.get(wanted0).then(bin => { - const wanted1 = missingKeys(wanted0, bin, outbin); - if ( wanted1 === undefined ) { return; } - return extensionStorage.get(wanted1).then(bin => { - const wanted2 = missingKeys(wanted1, bin, outbin); - if ( wanted2 === undefined ) { return; } - if ( argbin instanceof Object === false ) { return; } - if ( Array.isArray(argbin) ) { return; } - for ( const key of wanted2 ) { - if ( argbin.hasOwnProperty(key) === false ) { continue; } - outbin[key] = argbin[key]; - } - }); + return exGet(cacheAPI, keysFromGetArg(argbin), outbin).then(wanted => { + if ( wanted === undefined ) { return; } + return exGet(extensionStorage, wanted, outbin); + }).then(wanted => { + if ( wanted === undefined ) { return; } + if ( argbin instanceof Object === false ) { return; } + if ( Array.isArray(argbin) ) { return; } + for ( const key of wanted ) { + if ( argbin.hasOwnProperty(key) === false ) { continue; } + outbin[key] = argbin[key]; + } }).then(( ) => { const promises = []; for ( const key of Object.keys(outbin) ) { @@ -147,17 +146,14 @@ const cacheStorage = (( ) => { async set(keyvalStore) { const keys = Object.keys(keyvalStore); if ( keys.length === 0 ) { return; } + const bin = {}; const promises = []; for ( const key of keys ) { - promises.push(compress(key, keyvalStore[key])); + promises.push(compress(bin, key, keyvalStore[key])); } - const results = await Promise.all(promises); - const serializedStore = {}; - for ( const { key, data } of results ) { - serializedStore[key] = data; - } - cacheAPI.set(shouldCache(serializedStore)); - return extensionStorage.set(serializedStore).catch(reason => { + await Promise.all(promises); + cacheAPI.set(shouldCache(bin)); + return extensionStorage.set(bin).catch(reason => { ubolog(reason); }); }, @@ -361,6 +357,54 @@ const cacheAPI = (( ) => { }; })(); +/******************************************************************************* + * + * In-memory storage + * + * */ + +const memoryStorage = (( ) => { + + const sessionStorage = webext.storage.session; + + return { + get(...args) { + return sessionStorage.get(...args).catch(reason => { + ubolog(reason); + }); + }, + + async keys(regex) { + const results = await sessionStorage.get(null).catch(( ) => {}); + const keys = new Set(results[0]); + const bin = results[1] || {}; + for ( const key of Object.keys(bin) ) { + if ( regex && regex.test(key) === false ) { continue; } + keys.add(key); + } + return keys; + }, + + async set(...args) { + return sessionStorage.set(...args).catch(reason => { + ubolog(reason); + }); + }, + + remove(...args) { + return sessionStorage.remove(...args).catch(reason => { + ubolog(reason); + }); + }, + + clear(...args) { + return sessionStorage.clear(...args).catch(reason => { + ubolog(reason); + }); + }, + }; +})(); + /******************************************************************************* * * IndexedDB diff --git a/src/js/scuo-serializer.js b/src/js/scuo-serializer.js index f5f18fdd4e328..c0154fd9d4702 100644 --- a/src/js/scuo-serializer.js +++ b/src/js/scuo-serializer.js @@ -1148,10 +1148,11 @@ const THREAD_DESERIALIZE = 4; class MainThread { constructor() { + this.name = 'main'; this.jobs = []; this.workload = 0; this.timer = undefined; - this.busy = false; + this.busy = 2; } process() { @@ -1164,7 +1165,9 @@ class MainThread { job.resolve(result); this.processAsync(); if ( this.jobs.length === 0 ) { - this.busy = false; + this.busy = 2; + } else if ( this.busy > 2 ) { + this.busy -= 1; } } @@ -1174,11 +1177,12 @@ class MainThread { this.timer = globalThis.requestIdleCallback(deadline => { this.timer = undefined; globalThis.queueMicrotask(( ) => { - this.timer = undefined; this.process(); }); - this.busy = deadline.timeRemaining() === 0; - }, { timeout: 7 }); + if ( deadline.timeRemaining() === 0 ) { + this.busy += 1; + } + }, { timeout: 5 }); } serialize(data, options) { @@ -1199,16 +1203,17 @@ class MainThread { } get queueSize() { - return this.jobs.length + 1; + return this.jobs.length; } get workSize() { - return this.busy ? Number.MAX_SAFE_INTEGER : this.workload * 2; + return this.workload * this.busy; } } class Thread { constructor(gcer) { + this.name = 'worker'; this.jobs = new Map(); this.jobIdGenerator = 1; this.workload = 0; @@ -1263,7 +1268,10 @@ class Thread { } onmessage(ev) { - const job = ev.data; + this.ondone(ev.data); + } + + ondone(job) { const resolve = this.jobs.get(job.id); if ( resolve === undefined ) { return; } this.jobs.delete(job.id); @@ -1274,37 +1282,35 @@ class Thread { } async serialize(data, options) { - this.workerAccessTime = Date.now(); - const worker = await this.workerPromise; - if ( worker === null ) { - const result = serialize(data, options); - this.countdownWorker(); - return result; - } - const id = this.jobIdGenerator++; return new Promise(resolve => { + const id = this.jobIdGenerator++; this.workload += 1; - const job = { what: THREAD_SERIALIZE, id, data, options, size: 1 }; - this.jobs.set(job.id, resolve); - worker.postMessage(job); + this.jobs.set(id, resolve); + return this.workerPromise.then(worker => { + this.workerAccessTime = Date.now(); + if ( worker === null ) { + this.ondone({ id, result: serialize(data, options), size: 1 }); + } else { + worker.postMessage({ what: THREAD_SERIALIZE, id, data, options, size: 1 }); + } + }); }); } async deserialize(data, options) { - this.workerAccessTime = Date.now(); - const worker = await this.workerPromise; - if ( worker === null ) { - const result = deserialize(data, options); - this.countdownWorker(); - return result; - } - const id = this.jobIdGenerator++; return new Promise(resolve => { + const id = this.jobIdGenerator++; const size = data.length; this.workload += size; - const job = { what: THREAD_DESERIALIZE, id, data, options, size }; - this.jobs.set(job.id, resolve); - worker.postMessage(job); + this.jobs.set(id, resolve); + return this.workerPromise.then(worker => { + this.workerAccessTime = Date.now(); + if ( worker === null ) { + this.ondone({ id, result: deserialize(data, options), size }); + } else { + worker.postMessage({ what: THREAD_DESERIALIZE, id, data, options, size }); + } + }); }); } @@ -1323,12 +1329,11 @@ const threads = { const poolSize = this.pool.length; if ( poolSize !== 0 && poolSize >= maxPoolSize ) { if ( poolSize === 1 ) { return this.pool[0]; } - return this.pool.reduce((best, candidate) => { - if ( candidate.queueSize === 0 ) { return candidate; } - if ( best.queueSize === 0 ) { return best; } - return candidate.workSize < best.workSize - ? candidate - : best; + return this.pool.reduce((a, b) => { + //console.log(`${a.name}: q=${a.queueSize} w=${a.workSize} ${b.name}: q=${b.queueSize} w=${b.workSize}`); + if ( b.queueSize === 0 ) { return b; } + if ( a.queueSize === 0 ) { return a; } + return b.workSize < a.workSize ? b : a; }); } const thread = new Thread(thread => { @@ -1346,9 +1351,9 @@ export async function serializeAsync(data, options = {}) { if ( maxThreadCount === 0 ) { return serialize(data, options); } - const result = await threads - .thread(maxThreadCount) - .serialize(data, options); + const thread = threads.thread(maxThreadCount); + //console.log(`serializeAsync: thread=${thread.name} workload=${thread.workSize}`); + const result = await thread.serialize(data, options); if ( result !== undefined ) { return result; } return serialize(data, options); } @@ -1359,9 +1364,9 @@ export async function deserializeAsync(data, options = {}) { if ( maxThreadCount === 0 ) { return deserialize(data, options); } - const result = await threads - .thread(maxThreadCount) - .deserialize(data, options); + const thread = threads.thread(maxThreadCount); + //console.log(`deserializeAsync: thread=${thread.name} data=${data.length} workload=${thread.workSize}`); + const result = await thread.deserialize(data, options); if ( result !== undefined ) { return result; } return deserialize(data, options); } From 50271e2ba0624b4e2cff146290c01838aad9f910 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 27 Feb 2024 21:47:42 -0500 Subject: [PATCH 026/180] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index dd26cb9e3e0d4..cd8d482177b0d 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.56.1.3 +1.56.1.4 From ad03f7d86a69533665e840e10845fec29ad630ed Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 27 Feb 2024 21:56:01 -0500 Subject: [PATCH 027/180] Make Firefox dev build auto-update --- dist/firefox/updates.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index 00c522d26b907..38066820f0fc2 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,9 +3,9 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.56.1.3", + "version": "1.56.1.4", "browser_specific_settings": { "gecko": { "strict_min_version": "78.0" } }, - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b3/uBlock0_1.56.1b3.firefox.signed.xpi" + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b4/uBlock0_1.56.1b4.firefox.signed.xpi" } ] } From 3c299b86328f9f90560627394a0a9f418d039d0f Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 28 Feb 2024 12:14:59 -0500 Subject: [PATCH 028/180] Remove trusted-source requireement when using `badfilter` Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/3136 --- src/js/static-filtering-parser.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index 34828bc7af71b..491cf036816a0 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -1299,6 +1299,7 @@ export class AstFilterParser { let modifierType = 0; let requestTypeCount = 0; let unredirectableTypeCount = 0; + let badfilter = false; for ( let i = 0, n = this.nodeTypeRegisterPtr; i < n; i++ ) { const type = this.nodeTypeRegister[i]; const targetNode = this.nodeTypeLookupTable[type]; @@ -1322,6 +1323,8 @@ export class AstFilterParser { realBad = hasValue; break; case NODE_TYPE_NET_OPTION_NAME_BADFILTER: + badfilter = true; + /* falls through */ case NODE_TYPE_NET_OPTION_NAME_NOOP: realBad = isNegated || hasValue; break; @@ -1462,6 +1465,9 @@ export class AstFilterParser { this.addFlags(AST_FLAG_HAS_ERROR); } } + const requiresTrustedSource = ( ) => + this.options.trustedSource !== true && + isException === false && badfilter === false; switch ( modifierType ) { case NODE_TYPE_NET_OPTION_NAME_CNAME: realBad = abstractTypeCount || behaviorTypeCount || requestTypeCount; @@ -1489,7 +1495,7 @@ export class AstFilterParser { case NODE_TYPE_NET_OPTION_NAME_REPLACE: { realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount; if ( realBad ) { break; } - if ( isException !== true && this.options.trustedSource !== true ) { + if ( requiresTrustedSource() ) { this.astError = AST_ERROR_UNTRUSTED_SOURCE; realBad = true; break; @@ -1504,7 +1510,7 @@ export class AstFilterParser { case NODE_TYPE_NET_OPTION_NAME_URLTRANSFORM: { realBad = abstractTypeCount || behaviorTypeCount || unredirectableTypeCount; if ( realBad ) { break; } - if ( isException !== true && this.options.trustedSource !== true ) { + if ( requiresTrustedSource() ) { this.astError = AST_ERROR_UNTRUSTED_SOURCE; realBad = true; break; From 9862446b106f1af5f9b568d9d79fc482f34889e7 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 28 Feb 2024 13:32:24 -0500 Subject: [PATCH 029/180] Various fine tuning code of recent commits --- platform/chromium/webext.js | 9 -- platform/common/vapi-background.js | 32 +------ src/js/background.js | 2 +- src/js/cachestorage.js | 131 +++++++++++++++++------------ src/js/redirect-engine.js | 2 +- src/js/scuo-serializer.js | 12 ++- src/js/start.js | 24 +++++- src/js/static-net-filtering.js | 1 + src/js/storage.js | 40 +++++---- 9 files changed, 131 insertions(+), 122 deletions(-) diff --git a/platform/chromium/webext.js b/platform/chromium/webext.js index a0c24c2289ee6..851b653e26dbd 100644 --- a/platform/chromium/webext.js +++ b/platform/chromium/webext.js @@ -156,15 +156,6 @@ if ( chrome.storage.sync instanceof Object ) { }; } -// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/session -webext.storage.session = { - clear: ( ) => Promise.resolve(), - get: ( ) => Promise.resolve(), - getBytesInUse: ( ) => Promise.resolve(), - remove: ( ) => Promise.resolve(), - set: ( ) => Promise.resolve(), -}; - // https://bugs.chromium.org/p/chromium/issues/detail?id=608854 if ( chrome.tabs.removeCSS instanceof Function ) { webext.tabs.removeCSS = promisifyNoFail(chrome.tabs, 'removeCSS'); diff --git a/platform/common/vapi-background.js b/platform/common/vapi-background.js index 08cfd58720470..83d04a9af90e0 100644 --- a/platform/common/vapi-background.js +++ b/platform/common/vapi-background.js @@ -109,7 +109,7 @@ vAPI.generateSecret = (size = 1) => { * * */ -vAPI.sessionStorage = { +vAPI.sessionStorage = webext.storage.session || { get() { return Promise.resolve({}); }, @@ -122,7 +122,6 @@ vAPI.sessionStorage = { clear() { return Promise.resolve(); }, - implemented: false, }; /******************************************************************************* @@ -137,46 +136,21 @@ vAPI.sessionStorage = { vAPI.storage = { get(key, ...args) { - if ( vAPI.sessionStorage.implemented !== true ) { - return webext.storage.local.get(key, ...args).catch(reason => { - console.log(reason); - }); - } - return vAPI.sessionStorage.get(key, ...args).then(bin => { - const size = Object.keys(bin).length; - if ( size === 1 && typeof key === 'string' && bin[key] === null ) { - return {}; - } - if ( size !== 0 ) { return bin; } - return webext.storage.local.get(key, ...args).then(bin => { - if ( bin instanceof Object === false ) { return bin; } - // Mirror empty result as null value in order to prevent - // from falling back to storage.local when there is no need. - const tomirror = Object.assign({}, bin); - if ( typeof key === 'string' && Object.keys(bin).length === 0 ) { - Object.assign(tomirror, { [key]: null }); - } - vAPI.sessionStorage.set(tomirror); - return bin; - }).catch(reason => { - console.log(reason); - }); + return webext.storage.local.get(key, ...args).catch(reason => { + console.log(reason); }); }, set(...args) { - vAPI.sessionStorage.set(...args); return webext.storage.local.set(...args).catch(reason => { console.log(reason); }); }, remove(...args) { - vAPI.sessionStorage.remove(...args); return webext.storage.local.remove(...args).catch(reason => { console.log(reason); }); }, clear(...args) { - vAPI.sessionStorage.clear(...args); return webext.storage.local.clear(...args).catch(reason => { console.log(reason); }); diff --git a/src/js/background.js b/src/js/background.js index 123fe5778555d..79cae6eb10c66 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -146,7 +146,7 @@ if ( vAPI.webextFlavor.soup.has('firefox') ) { } const µBlock = { // jshint ignore:line - wakeupReason: '', + alarmQueue: [], userSettingsDefault, userSettings: Object.assign({}, userSettingsDefault), diff --git a/src/js/cachestorage.js b/src/js/cachestorage.js index 056cd8b68df1a..5dcaffbec903c 100644 --- a/src/js/cachestorage.js +++ b/src/js/cachestorage.js @@ -45,34 +45,6 @@ const keysFromGetArg = arg => { return Object.keys(arg); }; -// Cache API is subject to quota so we will use it only for what is key -// performance-wise -const shouldCache = bin => { - const out = {}; - for ( const key of Object.keys(bin) ) { - if ( key.startsWith('cache/') ) { - if ( /^cache\/(compiled|selfie)\//.test(key) === false ) { continue; } - } - out[key] = bin[key]; - } - return out; -}; - -const exGet = (api, wanted, outbin) => { - return api.get(wanted).then(inbin => { - inbin = inbin || {}; - const found = Object.keys(inbin); - Object.assign(outbin, inbin); - if ( found.length === wanted.length ) { return; } - const missing = []; - for ( const key of wanted ) { - if ( outbin.hasOwnProperty(key) ) { continue; } - missing.push(key); - } - return missing; - }); -}; - /******************************************************************************* * * Extension storage @@ -83,12 +55,26 @@ const exGet = (api, wanted, outbin) => { const cacheStorage = (( ) => { + const exGet = (api, wanted, outbin) => { + return api.get(wanted).then(inbin => { + inbin = inbin || {}; + const found = Object.keys(inbin); + Object.assign(outbin, inbin); + if ( found.length === wanted.length ) { return; } + const missing = []; + for ( const key of wanted ) { + if ( outbin.hasOwnProperty(key) ) { continue; } + missing.push(key); + } + return missing; + }); + }; + const compress = async (bin, key, data) => { const µbhs = µb.hiddenSettings; - const isLarge = typeof data === 'string' && - data.length >= µbhs.cacheStorageCompressionThreshold; const after = await scuo.serializeAsync(data, { - compress: isLarge && µbhs.cacheStorageCompression, + compress: µbhs.cacheStorageCompression, + compressThreshold: µbhs.cacheStorageCompressionThreshold, multithreaded: µbhs.cacheStorageMultithread, }); bin[key] = after; @@ -104,7 +90,7 @@ const cacheStorage = (( ) => { }); }; - return { + const api = { get(argbin) { const outbin = {}; return exGet(cacheAPI, keysFromGetArg(argbin), outbin).then(wanted => { @@ -152,7 +138,8 @@ const cacheStorage = (( ) => { promises.push(compress(bin, key, keyvalStore[key])); } await Promise.all(promises); - cacheAPI.set(shouldCache(bin)); + memoryStorage.set(bin); + cacheAPI.set(bin); return extensionStorage.set(bin).catch(reason => { ubolog(reason); }); @@ -191,16 +178,18 @@ const cacheStorage = (( ) => { return Promise.all(toMigrate); }, }; -})(); -// Not all platforms support getBytesInUse -if ( extensionStorage.getBytesInUse instanceof Function ) { - cacheStorage.getBytesInUse = function(...args) { - return extensionStorage.getBytesInUse(...args).catch(reason => { - ubolog(reason); - }); - }; -} + // Not all platforms support getBytesInUse + if ( extensionStorage.getBytesInUse instanceof Function ) { + api.getBytesInUse = function(...args) { + return extensionStorage.getBytesInUse(...args).catch(reason => { + ubolog(reason); + }); + }; + } + + return api; +})(); /******************************************************************************* * @@ -234,6 +223,19 @@ const cacheAPI = (( ) => { const urlToKey = url => decodeURIComponent(url.slice(urlPrefix.length)); + // Cache API is subject to quota so we will use it only for what is key + // performance-wise + const shouldCache = bin => { + const out = {}; + for ( const key of Object.keys(bin) ) { + if ( key.startsWith('cache/' ) ) { + if ( /^cache\/(compiled|selfie)\//.test(key) === false ) { continue; } + } + out[key] = bin[key]; + } + if ( Object.keys(out).length !== 0 ) { return out; } + }; + const getOne = async key => { const cache = await cacheStoragePromise; if ( cache === null ) { return; } @@ -327,12 +329,13 @@ const cacheAPI = (( ) => { ).catch(( ) => []); }, - set(keyvalStore) { - const keys = Object.keys(keyvalStore); - if ( keys.length === 0 ) { return; } + async set(...args) { + const bin = shouldCache(...args); + if ( bin === undefined ) { return; } + const keys = Object.keys(bin); const promises = []; for ( const key of keys ) { - promises.push(setOne(key, keyvalStore[key])); + promises.push(setOne(key, bin[key])); } return Promise.all(promises); }, @@ -363,30 +366,46 @@ const cacheAPI = (( ) => { * * */ -const memoryStorage = (( ) => { +const memoryStorage = (( ) => { // jshint ignore:line - const sessionStorage = webext.storage.session; + const sessionStorage = vAPI.sessionStorage; + + // This should help speed up loading from suspended state in Firefox for + // Android. + // 20240228 Observation: Slows down loading from suspended state in + // Firefox desktop. Could be different in Firefox for Android. + const shouldCache = bin => { + const out = {}; + for ( const key of Object.keys(bin) ) { + if ( key.startsWith('cache/compiled/') ) { continue; } + out[key] = bin[key]; + } + if ( Object.keys(out).length !== 0 ) { return out; } + }; return { get(...args) { - return sessionStorage.get(...args).catch(reason => { + return sessionStorage.get(...args).then(bin => { + return bin; + }).catch(reason => { ubolog(reason); }); }, async keys(regex) { - const results = await sessionStorage.get(null).catch(( ) => {}); - const keys = new Set(results[0]); - const bin = results[1] || {}; - for ( const key of Object.keys(bin) ) { + const bin = await sessionStorage.get(null).catch(( ) => {}); + const keys = []; + for ( const key of Object.keys(bin || {}) ) { if ( regex && regex.test(key) === false ) { continue; } - keys.add(key); + keys.push(key); } return keys; }, async set(...args) { - return sessionStorage.set(...args).catch(reason => { + const bin = shouldCache(...args); + if ( bin === undefined ) { return; } + return sessionStorage.set(bin).catch(reason => { ubolog(reason); }); }, @@ -522,7 +541,7 @@ const idbStorage = (( ) => { if ( entry.value instanceof Blob === false ) { return; } promises.push(decompress(keyvalStore, key, value)); }).catch(reason => { - ubolog(`cacheStorage.getAllFromDb() failed: ${reason}`); + ubolog(`idbStorage.getAllFromDb() failed: ${reason}`); callback(); }); }; diff --git a/src/js/redirect-engine.js b/src/js/redirect-engine.js index 7d70e35ee3c11..1edb37624c582 100644 --- a/src/js/redirect-engine.js +++ b/src/js/redirect-engine.js @@ -72,7 +72,7 @@ const warSecret = typeof vAPI === 'object' && vAPI !== null : ( ) => ''; const RESOURCES_SELFIE_VERSION = 7; -const RESOURCES_SELFIE_NAME = 'compiled/redirectEngine/resources'; +const RESOURCES_SELFIE_NAME = 'selfie/redirectEngine/resources'; /******************************************************************************/ /******************************************************************************/ diff --git a/src/js/scuo-serializer.js b/src/js/scuo-serializer.js index c0154fd9d4702..81b688547ee72 100644 --- a/src/js/scuo-serializer.js +++ b/src/js/scuo-serializer.js @@ -281,6 +281,12 @@ const isInstanceOf = (o, s) => { ); }; +const shouldCompress = (s, options) => + options.compress === true && ( + options.compressThreshold === undefined || + options.compressThreshold <= s.length + ); + /******************************************************************************* * * A large Uint is always a positive integer (can be zero), assumed to be @@ -1051,10 +1057,9 @@ export const serialize = (data, options = {}) => { const s = writeBuffer.join(''); writeRefs.clear(); writeBuffer.length = 0; - if ( options.compress !== true ) { return s; } + if ( shouldCompress(s, options) === false ) { return s; } const lz4Util = new LZ4BlockJS(); - const encoder = new TextEncoder(); - const uint8ArrayBefore = encoder.encode(s); + const uint8ArrayBefore = textEncoder.encode(s); const uint8ArrayAfter = lz4Util.encode(uint8ArrayBefore, 0); const lz4 = { size: uint8ArrayBefore.length, @@ -1145,7 +1150,6 @@ const THREAD_IAMREADY = 2; const THREAD_SERIALIZE = 3; const THREAD_DESERIALIZE = 4; - class MainThread { constructor() { this.name = 'main'; diff --git a/src/js/start.js b/src/js/start.js index 877d909c4d92d..caa27dfbf0a01 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -76,6 +76,10 @@ vAPI.app.onShutdown = ( ) => { permanentSwitches.reset(); }; +vAPI.alarms.onAlarm.addListener(alarm => { + µb.alarmQueue.push(alarm.name); +}); + /******************************************************************************/ // This is called only once, when everything has been loaded in memory after @@ -160,6 +164,13 @@ const onVersionReady = async lastVersion => { await cacheStorage.migrate(µb.hiddenSettings.cacheStorageAPI); } + // Remove cache items with obsolete names + if ( lastVersionInt < vAPI.app.intFromVersion('1.56.1b5') ) { + io.remove(`compiled/${µb.pslAssetKey}`); + io.remove('compiled/redirectEngine/resources'); + io.remove('selfie/main'); + } + // Since built-in resources may have changed since last version, we // force a reload of all resources. redirectEngine.invalidateResourcesSelfie(io); @@ -436,7 +447,7 @@ let selfieIsValid = false; try { selfieIsValid = await µb.selfieManager.load(); if ( selfieIsValid === true ) { - ubolog(`Selfie ready ${Date.now()-vAPI.T0} ms after launch`); + ubolog(`Loaded filtering engine from selfie ${Date.now()-vAPI.T0} ms after launch`); } } catch (ex) { console.trace(ex); @@ -506,5 +517,16 @@ ubolog(`All ready ${µb.supportStats.allReadyAfter} after launch`); µb.isReadyResolve(); +// Process alarm queue +while ( µb.alarmQueue.length !== 0 ) { + const what = µb.alarmQueue.shift(); + ubolog(`Processing alarm event from suspended state: '${what}'`); + switch ( what ) { + case 'createSelfie': + µb.selfieManager.create(); + break; + } +} + // <<<<< end of async/await scope })(); diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index 86d042248c253..01afc3a1b54b8 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -4629,6 +4629,7 @@ FilterContainer.prototype.optimize = function(throttle = 0) { /******************************************************************************/ FilterContainer.prototype.toSelfie = function() { + this.optimize(0); bidiTrieOptimize(true); keyvalStore.setItem('SNFE.origHNTrieContainer.trieDetails', origHNTrieContainer.optimize() diff --git a/src/js/storage.js b/src/js/storage.js index 99116547e183d..7c29f14221c5b 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -1147,7 +1147,10 @@ onBroadcast(msg => { µb.loadRedirectResources = async function() { try { const success = await redirectEngine.resourcesFromSelfie(io); - if ( success === true ) { return true; } + if ( success === true ) { + ubolog('Loaded redirect/scriptlets resources from selfie'); + return true; + } const fetcher = (path, options = undefined) => { if ( path.startsWith('/web_accessible_resources/') ) { @@ -1220,8 +1223,11 @@ onBroadcast(msg => { } try { - const selfie = await io.fromCache(`compiled/${this.pslAssetKey}`); - if ( psl.fromSelfie(selfie) ) { return; } + const selfie = await io.fromCache(`selfie/${this.pslAssetKey}`); + if ( psl.fromSelfie(selfie) ) { + ubolog('Loaded PSL from selfie'); + return; + } } catch (reason) { ubolog(reason); } @@ -1235,7 +1241,8 @@ onBroadcast(msg => { µb.compilePublicSuffixList = function(content) { const psl = publicSuffixList; psl.parse(content, punycode.toASCII); - return io.toCache(`compiled/${this.pslAssetKey}`, psl.toSelfie()); + ubolog(`Loaded PSL from ${this.pslAssetKey}`); + return io.toCache(`selfie/${this.pslAssetKey}`, psl.toSelfie()); }; /******************************************************************************/ @@ -1255,7 +1262,7 @@ onBroadcast(msg => { if ( µb.inMemoryFilters.length !== 0 ) { return; } if ( Object.keys(µb.availableFilterLists).length === 0 ) { return; } await Promise.all([ - io.toCache('selfie/main', { + io.toCache('selfie/staticMain', { magic: µb.systemSettings.selfieMagic, availableFilterLists: µb.availableFilterLists, }), @@ -1268,11 +1275,11 @@ onBroadcast(msg => { ]); lz4Codec.relinquish(); µb.selfieIsInvalid = false; - ubolog(`Selfie was created`); + ubolog('Filtering engine selfie created'); }; const loadMain = async function() { - const selfie = await io.fromCache('selfie/main'); + const selfie = await io.fromCache('selfie/staticMain'); if ( selfie instanceof Object === false ) { return false; } if ( selfie.magic !== µb.systemSettings.selfieMagic ) { return false; } if ( selfie.availableFilterLists instanceof Object === false ) { return false; } @@ -1300,35 +1307,26 @@ onBroadcast(msg => { catch (reason) { ubolog(reason); } - ubolog('Selfie not available'); + ubolog('Filtering engine selfie not available'); destroy(); return false; }; const destroy = function(options = {}) { if ( µb.selfieIsInvalid === false ) { - io.remove(/^selfie\//, options); + io.remove(/^selfie\/static/, options); µb.selfieIsInvalid = true; - ubolog('Selfie marked for invalidation'); - } - if ( µb.wakeupReason === 'createSelfie' ) { - µb.wakeupReason = ''; - return createTimer.offon({ sec: 27 }); + ubolog('Filtering engine selfie marked for invalidation'); } vAPI.alarms.create('createSelfie', { - delayInMinutes: µb.hiddenSettings.selfieAfter + delayInMinutes: µb.hiddenSettings.selfieAfter + 0.5 }); createTimer.offon({ min: µb.hiddenSettings.selfieAfter }); }; const createTimer = vAPI.defer.create(create); - vAPI.alarms.onAlarm.addListener(alarm => { - if ( alarm.name !== 'createSelfie') { return; } - µb.wakeupReason = 'createSelfie'; - }); - - µb.selfieManager = { load, destroy }; + µb.selfieManager = { load, create, destroy }; } /******************************************************************************/ From d4efaf020b2e4e0afcc526e599c4c0b96cf3f640 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 28 Feb 2024 17:31:29 -0500 Subject: [PATCH 030/180] Make indexedDB to default fast cache by default Turns out it's currently the fastest among the three currently implemented (Cache, browser.storage.session, indexedDB). Possibly because indexedDB can natively persist structure-cloneable data, something uBO can now benefit with the work on abstracting away the limitations of various storages being limited to persist only text or JSON data. Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/2969 --- src/js/cachestorage.js | 400 ++++++++++++++++++++++++++++------------- src/js/start.js | 14 +- 2 files changed, 277 insertions(+), 137 deletions(-) diff --git a/src/js/cachestorage.js b/src/js/cachestorage.js index 5dcaffbec903c..84d1fa734301d 100644 --- a/src/js/cachestorage.js +++ b/src/js/cachestorage.js @@ -19,7 +19,7 @@ Home: https://github.com/gorhill/uBlock */ -/* global browser, indexedDB */ +/* global indexedDB */ 'use strict'; @@ -45,6 +45,8 @@ const keysFromGetArg = arg => { return Object.keys(arg); }; +let fastCache = 'indexedDB'; + /******************************************************************************* * * Extension storage @@ -93,7 +95,11 @@ const cacheStorage = (( ) => { const api = { get(argbin) { const outbin = {}; - return exGet(cacheAPI, keysFromGetArg(argbin), outbin).then(wanted => { + return exGet( + cacheAPIs[fastCache], + keysFromGetArg(argbin), + outbin + ).then(wanted => { if ( wanted === undefined ) { return; } return exGet(extensionStorage, wanted, outbin); }).then(wanted => { @@ -117,7 +123,7 @@ const cacheStorage = (( ) => { async keys(regex) { const results = await Promise.all([ - cacheAPI.keys(regex), + cacheAPIs[fastCache].keys(regex), extensionStorage.get(null).catch(( ) => {}), ]); const keys = new Set(results[0]); @@ -129,53 +135,43 @@ const cacheStorage = (( ) => { return keys; }, - async set(keyvalStore) { - const keys = Object.keys(keyvalStore); + async set(inbin) { + const keys = Object.keys(inbin); if ( keys.length === 0 ) { return; } const bin = {}; const promises = []; for ( const key of keys ) { - promises.push(compress(bin, key, keyvalStore[key])); + promises.push(compress(bin, key, inbin[key])); } await Promise.all(promises); - memoryStorage.set(bin); - cacheAPI.set(bin); + cacheAPIs[fastCache].set(inbin, bin); return extensionStorage.set(bin).catch(reason => { ubolog(reason); }); }, remove(...args) { - cacheAPI.remove(...args); + cacheAPIs[fastCache].remove(...args); return extensionStorage.remove(...args).catch(reason => { ubolog(reason); }); }, clear(...args) { - cacheAPI.clear(...args); + cacheAPIs[fastCache].clear(...args); return extensionStorage.clear(...args).catch(reason => { ubolog(reason); }); }, - async migrate(cacheAPI) { - if ( cacheAPI === 'browser.storage.local' ) { return; } - if ( cacheAPI !== 'indexedDB' ) { - if ( vAPI.webextFlavor.soup.has('firefox') === false ) { return; } - } - if ( browser.extension.inIncognitoContext ) { return; } - // Copy all items to new cache storage - const bin = await idbStorage.get(null); - if ( typeof bin !== 'object' || bin === null ) { return; } - const toMigrate = []; - for ( const key of Object.keys(bin) ) { - if ( key.startsWith('cache/selfie/') ) { continue; } - ubolog(`Migrating ${key}=${JSON.stringify(bin[key]).slice(0,32)}`); - toMigrate.push(cacheStorage.set({ [key]: bin[key] })); + select(api) { + if ( cacheAPIs.hasOwnProperty(api) === false ) { return fastCache; } + fastCache = api; + for ( const k of Object.keys(cacheAPIs) ) { + if ( k === api ) { continue; } + cacheAPIs[k]['clear'](); } - idbStorage.clear(); - return Promise.all(toMigrate); + return fastCache; }, }; @@ -203,17 +199,23 @@ const cacheStorage = (( ) => { const cacheAPI = (( ) => { const caches = globalThis.caches; - const cacheStoragePromise = new Promise(resolve => { - if ( typeof caches !== 'object' || caches === null ) { - ubolog('CacheStorage API not available'); - resolve(null); - return; - } - resolve(caches.open(STORAGE_NAME).catch(reason => { + let cacheStoragePromise; + + const getAPI = ( ) => { + if ( cacheStoragePromise !== undefined ) { return cacheStoragePromise; } + cacheStoragePromise = new Promise(resolve => { + if ( typeof caches !== 'object' || caches === null ) { + ubolog('CacheStorage API not available'); + resolve(null); + return; + } + resolve(caches.open(STORAGE_NAME)); + }).catch(reason => { ubolog(reason); return null; - })); - }); + }); + return cacheStoragePromise; + }; const urlPrefix = 'https://ublock0.invalid/'; @@ -237,7 +239,7 @@ const cacheAPI = (( ) => { }; const getOne = async key => { - const cache = await cacheStoragePromise; + const cache = await getAPI(); if ( cache === null ) { return; } return cache.match(keyToURL(key)).then(response => { if ( response === undefined ) { return; } @@ -251,7 +253,7 @@ const cacheAPI = (( ) => { }; const getAll = async ( ) => { - const cache = await cacheStoragePromise; + const cache = await getAPI(); if ( cache === null ) { return; } return cache.keys().then(requests => { const promises = []; @@ -274,7 +276,7 @@ const cacheAPI = (( ) => { const setOne = async (key, text) => { if ( text === undefined ) { return removeOne(key); } const blob = new Blob([ text ], { type: 'text/plain;charset=utf-8'}); - const cache = await cacheStoragePromise; + const cache = await getAPI(); if ( cache === null ) { return; } return cache .put(keyToURL(key), new Response(blob)) @@ -284,7 +286,7 @@ const cacheAPI = (( ) => { }; const removeOne = async key => { - const cache = await cacheStoragePromise; + const cache = await getAPI(); if ( cache === null ) { return; } return cache.delete(keyToURL(key)).catch(reason => { ubolog(reason); @@ -321,7 +323,7 @@ const cacheAPI = (( ) => { }, async keys(regex) { - const cache = await cacheStoragePromise; + const cache = await getAPI(); if ( cache === null ) { return []; } return cache.keys().then(requests => requests.map(r => urlToKey(r.url)) @@ -329,8 +331,8 @@ const cacheAPI = (( ) => { ).catch(( ) => []); }, - async set(...args) { - const bin = shouldCache(...args); + async set(rawbin, serializedbin) { + const bin = shouldCache(serializedbin); if ( bin === undefined ) { return; } const keys = Object.keys(bin); const promises = []; @@ -352,11 +354,17 @@ const cacheAPI = (( ) => { return Promise.all(toRemove); }, - clear() { + async clear() { + if ( typeof caches !== 'object' || caches === null ) { return; } return globalThis.caches.delete(STORAGE_NAME).catch(reason => { ubolog(reason); }); }, + + shutdown() { + cacheStoragePromise = undefined; + return this.clear(); + }, }; })(); @@ -366,7 +374,7 @@ const cacheAPI = (( ) => { * * */ -const memoryStorage = (( ) => { // jshint ignore:line +const memoryStorage = (( ) => { const sessionStorage = vAPI.sessionStorage; @@ -393,7 +401,7 @@ const memoryStorage = (( ) => { // jshint ignore:line }, async keys(regex) { - const bin = await sessionStorage.get(null).catch(( ) => {}); + const bin = await this.get(null); const keys = []; for ( const key of Object.keys(bin || {}) ) { if ( regex && regex.test(key) === false ) { continue; } @@ -402,8 +410,8 @@ const memoryStorage = (( ) => { // jshint ignore:line return keys; }, - async set(...args) { - const bin = shouldCache(...args); + async set(rawbin, serializedbin) { + const bin = shouldCache(serializedbin); if ( bin === undefined ) { return; } return sessionStorage.set(bin).catch(reason => { ubolog(reason); @@ -421,6 +429,10 @@ const memoryStorage = (( ) => { // jshint ignore:line ubolog(reason); }); }, + + shutdown() { + return this.clear(); + }, }; })(); @@ -438,28 +450,8 @@ const idbStorage = (( ) => { const getDb = function() { if ( dbPromise !== undefined ) { return dbPromise; } dbPromise = new Promise(resolve => { - let req; - try { - req = indexedDB.open(STORAGE_NAME, 1); - if ( req.error ) { - ubolog(req.error); - req = undefined; - } - } catch(ex) { - } - if ( req === undefined ) { - return resolve(null); - } - req.onupgradeneeded = function(ev) { - // https://github.com/uBlockOrigin/uBlock-issues/issues/2725 - // If context Firefox + incognito mode, fall back to - // browser.storage.local for cache storage purpose. - if ( - vAPI.webextFlavor.soup.has('firefox') && - browser.extension.inIncognitoContext === true - ) { - return req.onerror(); - } + const req = indexedDB.open(STORAGE_NAME, 1); + req.onupgradeneeded = ev => { if ( ev.oldVersion === 1 ) { return; } try { const db = ev.target.result; @@ -468,27 +460,44 @@ const idbStorage = (( ) => { req.onerror(); } }; - req.onsuccess = function(ev) { + req.onsuccess = ev => { if ( resolve === undefined ) { return; } - req = undefined; - resolve(ev.target.result); + resolve(ev.target.result || null); resolve = undefined; }; - req.onerror = req.onblocked = function() { + req.onerror = req.onblocked = ( ) => { if ( resolve === undefined ) { return; } + ubolog(req.error); resolve(null); resolve = undefined; }; - vAPI.defer.once(5000).then(( ) => { + vAPI.defer.once(10000).then(( ) => { if ( resolve === undefined ) { return; } resolve(null); resolve = undefined; }); + }).catch(reason => { + ubolog(`idbStorage() / getDb() failed: ${reason}`); + return null; }); return dbPromise; }; - const fromBlob = function(data) { + // Cache API is subject to quota so we will use it only for what is key + // performance-wise + const shouldCache = bin => { + const out = {}; + for ( const key of Object.keys(bin) ) { + if ( key.startsWith('cache/' ) ) { + if ( /^cache\/(compiled|selfie)\//.test(key) === false ) { continue; } + } + out[key] = bin[key]; + } + if ( Object.keys(out).length === 0 ) { return; } + return out; + }; + + const fromBlob = data => { if ( data instanceof Blob === false ) { return Promise.resolve(data); } @@ -501,83 +510,216 @@ const idbStorage = (( ) => { }); }; - const decompress = function(store, key, data) { - return lz4Codec.decode(data, fromBlob).then(data => { - store[key] = data; + const decompress = (key, value) => { + return lz4Codec.decode(value, fromBlob).then(value => { + return { key, value }; }); }; - const visitAllFromDb = async function(visitFn) { + const getAllEntries = async function() { 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(); - }; + if ( db === null ) { return []; } + return new Promise(resolve => { + const entries = []; + const transaction = db.transaction(STORAGE_NAME, 'readonly'); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = ( ) => { + resolve(Promise.all(entries)); + }; + const table = transaction.objectStore(STORAGE_NAME); + const req = table.openCursor(); + req.onsuccess = ev => { + const cursor = ev.target && ev.target.result; + if ( !cursor ) { return; } + const { key, value } = cursor.value; + if ( value instanceof Blob ) { + entries.push(decompress(key, value)); + } else { + entries.push({ key, value }); + } + cursor.continue(); + }; + }).catch(reason => { + ubolog(`idbStorage() / getAllEntries() failed: ${reason}`); + return []; + }); }; - 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 getAllKeys = async function() { + const db = await getDb(); + if ( db === null ) { return []; } + return new Promise(resolve => { + const keys = []; + const transaction = db.transaction(STORAGE_NAME, 'readonly'); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = ( ) => { + resolve(keys); + }; + const table = transaction.objectStore(STORAGE_NAME); + const req = table.openCursor(); + req.onsuccess = ev => { + const cursor = ev.target && ev.target.result; + if ( !cursor ) { return; } + keys.push(cursor.key); + cursor.continue(); + }; + }).catch(reason => { + ubolog(`idbStorage() / getAllKeys() failed: ${reason}`); + return []; + }); + }; + + const getEntries = async function(keys) { + const db = await getDb(); + if ( db === null ) { return []; } + return new Promise(resolve => { + const entries = []; + const gotOne = ev => { + const { result } = ev.target; + if ( typeof result !== 'object' ) { return; } + if ( result === null ) { return; } + const { key, value } = result; + if ( value instanceof Blob ) { + entries.push(decompress(key, value)); + } else { + entries.push({ key, value }); + } + }; + const transaction = db.transaction(STORAGE_NAME, 'readonly'); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = ( ) => { + resolve(Promise.all(entries)); + }; + const table = transaction.objectStore(STORAGE_NAME); + for ( const key of keys ) { + const req = table.get(key); + req.onsuccess = gotOne; + req.onerror = ( ) => { }; } - const { key, value } = entry; - keyvalStore[key] = value; - if ( entry.value instanceof Blob === false ) { return; } - promises.push(decompress(keyvalStore, key, value)); }).catch(reason => { - ubolog(`idbStorage.getAllFromDb() failed: ${reason}`); - callback(); + ubolog(`idbStorage() / getEntries() failed: ${reason}`); + return []; }); }; - const clearDb = async function(callback) { - if ( typeof callback !== 'function' ) { - callback = ()=>{}; - } - try { - const db = await getDb(); - if ( !db ) { return callback(); } - db.close(); - indexedDB.deleteDatabase(STORAGE_NAME); - callback(); - } - catch(reason) { - callback(); + const getAll = async ( ) => { + const entries = await getAllEntries(); + const outbin = {}; + for ( const { key, value } of entries ) { + outbin[key] = value; } + return outbin; + }; + + const setEntries = async inbin => { + const keys = Object.keys(inbin); + if ( keys.length === 0 ) { return; } + const db = await getDb(); + if ( db === null ) { return; } + return new Promise(resolve => { + const entries = []; + for ( const key of keys ) { + entries.push({ key, value: inbin[key] }); + } + const transaction = db.transaction(STORAGE_NAME, 'readwrite'); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = ( ) => { + resolve(); + }; + const table = transaction.objectStore(STORAGE_NAME); + for ( const entry of entries ) { + table.put(entry); + } + }).catch(reason => { + ubolog(`idbStorage() / setEntries() failed: ${reason}`); + }); + }; + + const deleteEntries = async arg => { + const keys = Array.isArray(arg) ? arg.slice() : [ arg ]; + if ( keys.length === 0 ) { return; } + const db = await getDb(); + if ( db === null ) { return; } + return new Promise(resolve => { + const transaction = db.transaction(STORAGE_NAME, 'readwrite'); + transaction.oncomplete = + transaction.onerror = + transaction.onabort = ( ) => { + resolve(); + }; + const table = transaction.objectStore(STORAGE_NAME); + for ( const key of keys ) { + table.delete(key); + } + }).catch(reason => { + ubolog(`idbStorage() / deleteEntries() failed: ${reason}`); + }); }; return { - get: function get() { - return new Promise(resolve => { - return getAllFromDb(bin => resolve(bin)); - }); + async get(argbin) { + const keys = keysFromGetArg(argbin); + if ( keys === undefined ) { return; } + if ( keys.length === 0 ) { return getAll(); } + const entries = await getEntries(keys); + const outbin = {}; + for ( const { key, value } of entries ) { + outbin[key] = value; + } + if ( argbin instanceof Object && Array.isArray(argbin) === false ) { + for ( const key of keys ) { + if ( outbin.hasOwnProperty(key) ) { continue; } + outbin[key] = argbin[key]; + } + } + return outbin; + }, + + async set(rawbin) { + const bin = shouldCache(rawbin); + if ( bin === undefined ) { return; } + return setEntries(bin); + }, + + keys() { + return getAllKeys(); }, - clear: function clear() { - return new Promise(resolve => { - clearDb(( ) => resolve()); + + remove(...args) { + return deleteEntries(...args); + }, + + clear() { + return getDb().then(db => { + if ( db === null ) { return; } + db.close(); + indexedDB.deleteDatabase(STORAGE_NAME); + }).catch(reason => { + ubolog(`idbStorage.clear() failed: ${reason}`); }); }, + + async shutdown() { + await this.clear(); + dbPromise = undefined; + }, }; })(); /******************************************************************************/ +const cacheAPIs = { + 'indexedDB': idbStorage, + 'cacheAPI': cacheAPI, + 'browser.storage.session': memoryStorage, +}; + +/******************************************************************************/ + export default cacheStorage; /******************************************************************************/ diff --git a/src/js/start.js b/src/js/start.js index caa27dfbf0a01..bd0bd46603475 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -159,11 +159,6 @@ const onVersionReady = async lastVersion => { return; } - // Migrate cache storage - if ( lastVersionInt < vAPI.app.intFromVersion('1.56.1b1') ) { - await cacheStorage.migrate(µb.hiddenSettings.cacheStorageAPI); - } - // Remove cache items with obsolete names if ( lastVersionInt < vAPI.app.intFromVersion('1.56.1b5') ) { io.remove(`compiled/${µb.pslAssetKey}`); @@ -319,9 +314,6 @@ const onHiddenSettingsReady = async ( ) => { ubolog(`WASM modules ready ${Date.now()-vAPI.T0} ms after launch`); }); } - - // Maybe override default cache storage - µb.supportStats.cacheBackend = 'browser.storage.local'; }; /******************************************************************************/ @@ -401,6 +393,12 @@ try { const adminExtra = await vAPI.adminStorage.get('toAdd'); ubolog(`Extra admin settings ready ${Date.now()-vAPI.T0} ms after launch`); + // Maybe override default cache storage + µb.supportStats.cacheBackend = await cacheStorage.select( + µb.hiddenSettings.cacheStorageAPI + ); + ubolog(`Backend storage for cache will be ${µb.supportStats.cacheBackend}`); + const lastVersion = await vAPI.storage.get(createDefaultProps()).then(async fetched => { ubolog(`Version ready ${Date.now()-vAPI.T0} ms after launch`); await onVersionReady(fetched.version); From 0dc8cf6e8abaa82f3b0464415e31591a5d0457fb Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 28 Feb 2024 17:39:13 -0500 Subject: [PATCH 031/180] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index cd8d482177b0d..130990cf641b6 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.56.1.4 +1.56.1.5 From a2aa357dacf075ec4a6720cd96e70d8d3e649b2b Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 28 Feb 2024 17:42:38 -0500 Subject: [PATCH 032/180] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 293c743a4eaae..05650368c9c4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Fixes / changes +- [Remove trusted-source requirement when using `badfilter`](https://github.com/gorhill/uBlock/commit/3c299b8632) - [Redesign cache storage](https://github.com/gorhill/uBlock/commit/086766a924) - [Don't match network filter-derived regexes against non-network URIs](https://github.com/gorhill/uBlock/commit/2262a129ec) - [Remove obsolete trusted directives](https://github.com/gorhill/uBlock/commit/439a059cca) From c6706c198393f0f96c71a53bafa4ac5843f2c98d Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 28 Feb 2024 17:45:38 -0500 Subject: [PATCH 033/180] Make Firefox dev build auto-update --- dist/firefox/updates.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index 38066820f0fc2..bbdfba7426bde 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,9 +3,9 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.56.1.4", + "version": "1.56.1.5", "browser_specific_settings": { "gecko": { "strict_min_version": "78.0" } }, - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b4/uBlock0_1.56.1b4.firefox.signed.xpi" + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b5/uBlock0_1.56.1b5.firefox.signed.xpi" } ] } From b0067b79d508bf6565a10c053330be9c17d36af2 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 28 Feb 2024 18:53:27 -0500 Subject: [PATCH 034/180] Reset filter lists in worker when creating filters via "Block element" Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/968 --- src/js/storage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/storage.js b/src/js/storage.js index 7c29f14221c5b..6ac3866324bc9 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -625,6 +625,7 @@ onBroadcast(msg => { cosmeticFilteringEngine.removeFromSelectorCache( hostnameFromURI(details.docURL) ); + staticFilteringReverseLookup.resetLists(); }; µb.userFiltersAreEnabled = function() { From 2b16a10b8235d6880d57906f8c9e1423a1f8233a Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 07:51:08 -0500 Subject: [PATCH 035/180] First lookup matching stock lists when importing URLs Related discussion: https://github.com/MasterKia/PersianBlocker/discussions/224 --- src/js/3p-filters.js | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js index 8441df9da029b..18d417de5d2d6 100644 --- a/src/js/3p-filters.js +++ b/src/js/3p-filters.js @@ -530,6 +530,35 @@ dom.on('#lists', 'click', 'span.cache', onPurgeClicked); /******************************************************************************/ const selectFilterLists = async ( ) => { + // External filter lists to import + // Find stock list matching entries in lists to import + const toImport = (( ) => { + const textarea = qs$('#lists .listEntry[data-role="import"].expanded textarea'); + if ( textarea === null ) { return ''; } + const lists = listsetDetails.available; + const lines = textarea.value.split(/\s+\n|\s+/); + const after = []; + for ( const line of lines ) { + if ( /^https?:\/\//.test(line) === false ) { continue; } + for ( const [ listkey, list ] of Object.entries(lists) ) { + if ( list.content !== 'filters' ) { continue; } + if ( list.contentURL === undefined ) { continue; } + if ( list.contentURL.includes(line) === false ) { + after.push(line); + continue; + } + const input = qs$(`[data-key="${list.group}"] [data-key="${listkey}"] > .detailbar input`); + if ( input === null ) { break; } + input.checked = true; + toggleFilterList(input, true, true); + break; + } + } + dom.cl.remove(textarea.closest('.expandable'), 'expanded'); + textarea.value = ''; + return after.join('\n'); + })(); + // Cosmetic filtering switch let checked = qs$('#parseCosmeticFilters').checked; vAPI.messaging.send('dashboard', { @@ -569,14 +598,6 @@ const selectFilterLists = async ( ) => { } } - // External filter lists to import - const textarea = qs$('#lists .listEntry[data-role="import"].expanded textarea'); - const toImport = textarea !== null && textarea.value.trim() || ''; - if ( textarea !== null ) { - dom.cl.remove(textarea.closest('.expandable'), 'expanded'); - textarea.value = ''; - } - hashFromListsetDetails(); await vAPI.messaging.send('dashboard', { From ba95d2bc49316a0f1f0d798ee78624804021e5d5 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 08:37:44 -0500 Subject: [PATCH 036/180] Minor code review of last commit Related commit: https://github.com/gorhill/uBlock/commit/2b16a10b82 --- src/js/3p-filters.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js index 18d417de5d2d6..6050e02761dcd 100644 --- a/src/js/3p-filters.js +++ b/src/js/3p-filters.js @@ -547,10 +547,9 @@ const selectFilterLists = async ( ) => { after.push(line); continue; } - const input = qs$(`[data-key="${list.group}"] [data-key="${listkey}"] > .detailbar input`); - if ( input === null ) { break; } - input.checked = true; - toggleFilterList(input, true, true); + const listEntry = qs$(`[data-key="${list.group}"] [data-key="${listkey}"]`); + if ( listEntry === null ) { break; } + toggleFilterList(listEntry, true); break; } } From d7154de9e9ff24de9f098dbbb790fe29636d1a7b Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 09:18:07 -0500 Subject: [PATCH 037/180] Minor renaming of variables --- src/js/cachestorage.js | 12 ++++++------ src/js/scuo-serializer.js | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/js/cachestorage.js b/src/js/cachestorage.js index 84d1fa734301d..f1e940df76471 100644 --- a/src/js/cachestorage.js +++ b/src/js/cachestorage.js @@ -135,17 +135,17 @@ const cacheStorage = (( ) => { return keys; }, - async set(inbin) { - const keys = Object.keys(inbin); + async set(rawbin) { + const keys = Object.keys(rawbin); if ( keys.length === 0 ) { return; } - const bin = {}; + const serializedbin = {}; const promises = []; for ( const key of keys ) { - promises.push(compress(bin, key, inbin[key])); + promises.push(compress(serializedbin, key, rawbin[key])); } await Promise.all(promises); - cacheAPIs[fastCache].set(inbin, bin); - return extensionStorage.set(bin).catch(reason => { + cacheAPIs[fastCache].set(rawbin, serializedbin); + return extensionStorage.set(serializedbin).catch(reason => { ubolog(reason); }); }, diff --git a/src/js/scuo-serializer.js b/src/js/scuo-serializer.js index 81b688547ee72..82fbae1e05b41 100644 --- a/src/js/scuo-serializer.js +++ b/src/js/scuo-serializer.js @@ -224,7 +224,7 @@ const xtypeToSerializedInt = { '[object DataView]': I_DATAVIEW, }; -const typeToSerializedChar = { +const xtypeToSerializedChar = { '[object Int8Array]': C_INT8ARRAY, '[object Uint8Array]': C_UINT8ARRAY, '[object Uint8ClampedArray]': C_UINT8CLAMPEDARRAY, @@ -664,7 +664,7 @@ const _serialize = data => { case I_FLOAT32ARRAY: case I_FLOAT64ARRAY: writeBuffer.push( - typeToSerializedChar[xtypeName], + xtypeToSerializedChar[xtypeName], strFromLargeUint(data.byteOffset), strFromLargeUint(data.length) ); From 09bba3199e9876fe298bb8237efce16233164a75 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 09:48:01 -0500 Subject: [PATCH 038/180] Import translation work from https://crowdin.com/project/ublock --- platform/mv3/extension/_locales/bn/messages.json | 4 ++-- platform/mv3/extension/_locales/en/messages.json | 2 +- src/_locales/be/messages.json | 6 +++--- src/_locales/bn/messages.json | 4 ++-- src/_locales/eo/messages.json | 2 +- src/_locales/he/messages.json | 14 +++++++------- src/_locales/lt/messages.json | 14 +++++++------- src/_locales/ro/messages.json | 2 +- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/platform/mv3/extension/_locales/bn/messages.json b/platform/mv3/extension/_locales/bn/messages.json index ba12b5dec0d3b..0251eb13342ba 100644 --- a/platform/mv3/extension/_locales/bn/messages.json +++ b/platform/mv3/extension/_locales/bn/messages.json @@ -116,7 +116,7 @@ "description": "This describes the default filtering mode setting" }, "filteringMode0Name": { - "message": "ছাঁকনি নেই", + "message": "ফিল্টারিং নেই", "description": "Name of blocking mode 0" }, "filteringMode1Name": { @@ -152,7 +152,7 @@ "description": "The header text for the 'Behavior' section" }, "autoReloadLabel": { - "message": "ফিল্টারিং মোড পরিবর্তন করার সময় স্বয়ংক্রিয়ভাবে পেজ পুনরায় লোড করুন", + "message": "ফিল্টারিং মোড পরিবর্তন করার সময় স্বয়ংক্রিয়ভাবে পুনরায় লোড করুন", "description": "Label for a checkbox in the options page" } } diff --git a/platform/mv3/extension/_locales/en/messages.json b/platform/mv3/extension/_locales/en/messages.json index 6725ba1250feb..53106046e923f 100644 --- a/platform/mv3/extension/_locales/en/messages.json +++ b/platform/mv3/extension/_locales/en/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/src/_locales/be/messages.json b/src/_locales/be/messages.json index f68dfb39d6f5c..dd83edaaf4dc8 100644 --- a/src/_locales/be/messages.json +++ b/src/_locales/be/messages.json @@ -4,7 +4,7 @@ "description": "extension name." }, "extShortDesc": { - "message": "Нарэшце, эфектыўны блакавальнік. Не нагружае працэсар і памяць.", + "message": "Нарэшце, эфектыўны блакіроўшчык. Не нагружае працэсар і памяць.", "description": "this will be in the Chrome web store: must be 132 characters or less" }, "dashboardName": { @@ -20,7 +20,7 @@ "description": "Label for button to prevent navigating away from unsaved changes" }, "dashboardUnsavedWarningIgnore": { - "message": "Iгнараваць", + "message": "Ігнараваць", "description": "Label for button to ignore unsaved changes" }, "settingsPageName": { @@ -68,7 +68,7 @@ "description": "Title for the advanced settings page" }, "popupPowerSwitchInfo": { - "message": "Націсканне: адключыць/уключыць uBlock₀ для гэтага сайта.\n\nCtrl+націсканне: адключыць uBlock₀ толькі на гэтай старонцы.", + "message": "Націск: адключыць/уключыць uBlock₀ для гэтага сайта.\n\nCtrl+націск: адключыць uBlock₀ толькі на гэтай старонцы.", "description": "English: Click: disable/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page." }, "popupPowerSwitchInfo1": { diff --git a/src/_locales/bn/messages.json b/src/_locales/bn/messages.json index db9e74f79ddbb..e8d98b78e8a77 100644 --- a/src/_locales/bn/messages.json +++ b/src/_locales/bn/messages.json @@ -16,7 +16,7 @@ "description": "A warning in the dashboard when navigating away from unsaved changes" }, "dashboardUnsavedWarningStay": { - "message": "থাকুন", + "message": "এখানে থাকুন", "description": "Label for button to prevent navigating away from unsaved changes" }, "dashboardUnsavedWarningIgnore": { @@ -1252,7 +1252,7 @@ "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, "toggleJavascript": { - "message": "Toggle JavaScript", + "message": "Javascript টগল করুন", "description": "Label for keyboard shortcut used to toggle no-scripting switch" }, "relaxBlockingMode": { diff --git a/src/_locales/eo/messages.json b/src/_locales/eo/messages.json index 4b410a3ac52e5..d784fb8d38d27 100644 --- a/src/_locales/eo/messages.json +++ b/src/_locales/eo/messages.json @@ -1252,7 +1252,7 @@ "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, "toggleJavascript": { - "message": "Toggle JavaScript", + "message": "Baskuli Javascript-kodoj", "description": "Label for keyboard shortcut used to toggle no-scripting switch" }, "relaxBlockingMode": { diff --git a/src/_locales/he/messages.json b/src/_locales/he/messages.json index 721692d3e682c..166dde6fe7b03 100644 --- a/src/_locales/he/messages.json +++ b/src/_locales/he/messages.json @@ -480,7 +480,7 @@ "description": "Filter lists section name" }, "3pGroupMalware": { - "message": "ביטחון והגנה מפני נוזקה", + "message": "הגנה מפני נוזקות, אבטחה", "description": "Filter lists section name" }, "3pGroupAnnoyances": { @@ -532,15 +532,15 @@ "description": "Short information about how to create custom filters" }, "1pTrustWarning": { - "message": "Do not add filters from untrusted sources.", + "message": "אל תוסיף מסננים ממקורות לא מהימנים.", "description": "Warning against copy-pasting filters from random sources" }, "1pImport": { - "message": "ייבא וצרף", + "message": "ייבא וצרף…", "description": "Button in the 'My filters' pane" }, "1pExport": { - "message": "ייצוא", + "message": "ייצוא…", "description": "Button in the 'My filters' pane" }, "1pExportFilename": { @@ -584,7 +584,7 @@ "description": "" }, "rulesExport": { - "message": "ייצא לקובץ...", + "message": "ייצא לקובץ…", "description": "Button in the 'My rules' pane" }, "rulesDefaultFileName": { @@ -620,11 +620,11 @@ "description": "A concise description of the 'Trusted sites' pane." }, "whitelistImport": { - "message": "ייבא וצרף", + "message": "ייבא וצרף…", "description": "Button in the 'Trusted sites' pane" }, "whitelistExport": { - "message": "ייצוא", + "message": "ייצוא…", "description": "Button in the 'Trusted sites' pane" }, "whitelistExportFilename": { diff --git a/src/_locales/lt/messages.json b/src/_locales/lt/messages.json index a8071434e2b83..4bfb04f49c7e0 100644 --- a/src/_locales/lt/messages.json +++ b/src/_locales/lt/messages.json @@ -128,7 +128,7 @@ "description": "Tooltip used for the logger icon in the panel" }, "popupTipReport": { - "message": "Report an issue on this website", + "message": "Pranešti apie problemą šioje svetainėje", "description": "Tooltip used for the 'chat' icon in the panel" }, "popupTipNoPopups": { @@ -888,11 +888,11 @@ "description": "Text for button which open an external webpage in Support pane" }, "supportReportSpecificButton": { - "message": "Create new report", + "message": "Sukurti naują ataskaitą", "description": "Text for button which open an external webpage in Support pane" }, "supportFindSpecificButton": { - "message": "Find similar reports", + "message": "Rasti panašias ataskaitas", "description": "A clickable link in the filter issue reporter section" }, "supportS1H": { @@ -904,7 +904,7 @@ "description": "First paragraph of 'Documentation' section in Support pane" }, "supportS2H": { - "message": "Questions and support", + "message": "Klausimai ir pagalba", "description": "Header of 'Questions and support' section in Support pane" }, "supportS2P1": { @@ -1172,7 +1172,7 @@ "description": "English: Permanently" }, "docblockedDisable": { - "message": "Proceed", + "message": "Tęsti", "description": "Button text to navigate to the blocked page" }, "cloudPush": { @@ -1244,7 +1244,7 @@ "description": "Label for buttons used to copy something to the clipboard" }, "genericSelectAll": { - "message": "Select all", + "message": "Žymėti viską", "description": "Label for buttons used to select all text in editor" }, "toggleCosmeticFiltering": { @@ -1280,7 +1280,7 @@ "description": "Message used in frame placeholders" }, "linterMainReport": { - "message": "Errors: {{count}}", + "message": "Klaidos: {{count}}", "description": "Summary of number of errors as reported by the linter " }, "unprocessedRequestTooltip": { diff --git a/src/_locales/ro/messages.json b/src/_locales/ro/messages.json index f5faf91690686..2bf389ba6b919 100644 --- a/src/_locales/ro/messages.json +++ b/src/_locales/ro/messages.json @@ -1252,7 +1252,7 @@ "description": "Label for keyboard shortcut used to toggle cosmetic filtering" }, "toggleJavascript": { - "message": "Toggle JavaScript", + "message": "Comută JavaScript", "description": "Label for keyboard shortcut used to toggle no-scripting switch" }, "relaxBlockingMode": { From 059e4e5e28ccf4e2ccee9781053341b60b0c389c Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 11:43:51 -0500 Subject: [PATCH 039/180] Imrpove saving request stats for non-persistent background page Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/2969 Related previous commit: https://github.com/gorhill/uBlock/commit/5a338b7210 The save-to-storage period is back to being around ~4 minutes, but now browser.storage.session API is used to keep track of request stats should the extension be suspended before the period elapse. --- platform/common/vapi-background.js | 32 ++++++++++++------ src/js/background.js | 7 ++-- src/js/messaging.js | 4 +-- src/js/pagestore.js | 6 ++-- src/js/start.js | 10 +++--- src/js/storage.js | 54 +++++++++++++++++++++++------- 6 files changed, 73 insertions(+), 40 deletions(-) diff --git a/platform/common/vapi-background.js b/platform/common/vapi-background.js index 83d04a9af90e0..7f336066f664d 100644 --- a/platform/common/vapi-background.js +++ b/platform/common/vapi-background.js @@ -109,9 +109,9 @@ vAPI.generateSecret = (size = 1) => { * * */ -vAPI.sessionStorage = webext.storage.session || { +vAPI.sessionStorage = browser.storage.session || { get() { - return Promise.resolve({}); + return Promise.resolve(); }, set() { return Promise.resolve(); @@ -122,6 +122,7 @@ vAPI.sessionStorage = webext.storage.session || { clear() { return Promise.resolve(); }, + unavailable: true, }; /******************************************************************************* @@ -315,10 +316,10 @@ vAPI.Tabs = class { }); } - async executeScript() { + async executeScript(...args) { let result; try { - result = await webext.tabs.executeScript(...arguments); + result = await webext.tabs.executeScript(...args); } catch(reason) { } @@ -543,7 +544,7 @@ vAPI.Tabs = class { targetURL = vAPI.getURL(targetURL); } - vAPI.tabs.update(tabId, { url: targetURL }); + return vAPI.tabs.update(tabId, { url: targetURL }); } async remove(tabId) { @@ -1778,15 +1779,24 @@ vAPI.cloud = (( ) => { /******************************************************************************/ /******************************************************************************/ -vAPI.alarms = browser.alarms || { - create() { +vAPI.alarms = { + create(...args) { + browser.alarms.create(...args); }, - clear() { + createIfNotPresent(name, ...args) { + browser.alarms.get(name).then(details => { + if ( details !== undefined ) { return; } + browser.alarms.create(name, ...args); + }); + }, + async clear(...args) { + return browser.alarms.clear(...args); }, onAlarm: { - addListener() { - } - } + addListener(...args) { + browser.alarms.onAlarm.addListener(...args); + }, + }, }; /******************************************************************************/ diff --git a/src/js/background.js b/src/js/background.js index 79cae6eb10c66..601851924059c 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -174,11 +174,10 @@ const µBlock = { // jshint ignore:line 'moz-extension-scheme', ], - localSettings: { - blockedRequestCount: 0, - allowedRequestCount: 0, + requestStats: { + blockedCount: 0, + allowedCount: 0, }, - localSettingsLastModified: 0, // Read-only systemSettings: { diff --git a/src/js/messaging.js b/src/js/messaging.js index 468579a4f140b..f34443cf731b1 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -365,8 +365,8 @@ const popupDataFromTabId = function(tabId, tabTitle) { colorBlindFriendly: µbus.colorBlindFriendly, cosmeticFilteringSwitch: false, firewallPaneMinimized: µbus.firewallPaneMinimized, - globalAllowedRequestCount: µb.localSettings.allowedRequestCount, - globalBlockedRequestCount: µb.localSettings.blockedRequestCount, + globalAllowedRequestCount: µb.requestStats.allowedCount, + globalBlockedRequestCount: µb.requestStats.blockedCount, fontSize: µbhs.popupFontSize, godMode: µbhs.filterAuthorMode, netFilteringSwitch: false, diff --git a/src/js/pagestore.js b/src/js/pagestore.js index 907e747431200..838bd3aa655f7 100644 --- a/src/js/pagestore.js +++ b/src/js/pagestore.js @@ -739,10 +739,8 @@ const PageStore = class { aggregateAllowed += 1; } } - if ( aggregateAllowed !== 0 || aggregateBlocked !== 0 ) { - µb.localSettings.blockedRequestCount += aggregateBlocked; - µb.localSettings.allowedRequestCount += aggregateAllowed; - µb.localSettingsLastModified = now; + if ( aggregateAllowed || aggregateBlocked ) { + µb.incrementRequestStats(aggregateBlocked, aggregateAllowed); } journal.length = 0; } diff --git a/src/js/start.js b/src/js/start.js index bd0bd46603475..2bda75c1fd81c 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -327,7 +327,6 @@ const onFirstFetchReady = (fetched, adminExtra) => { } // Order is important -- do not change: - fromFetch(µb.localSettings, fetched); fromFetch(µb.restoreBackupSettings, fetched); permanentFirewall.fromString(fetched.dynamicFilteringString); @@ -362,14 +361,9 @@ const createDefaultProps = ( ) => { 'dynamicFilteringString': µb.dynamicFilteringDefault.join('\n'), 'urlFilteringString': '', 'hostnameSwitchesString': µb.hostnameSwitchesDefault.join('\n'), - 'lastRestoreFile': '', - 'lastRestoreTime': 0, - 'lastBackupFile': '', - 'lastBackupTime': 0, 'netWhitelist': µb.netWhitelistDefault, 'version': '0.0.0.0' }; - toFetch(µb.localSettings, fetchableProps); toFetch(µb.restoreBackupSettings, fetchableProps); return fetchableProps; }; @@ -424,6 +418,7 @@ try { ubolog(`Cache magic numbers ready ${Date.now()-vAPI.T0} ms after launch`); onCacheSettingsReady(bin); }), + µb.loadLocalSettings(), ]); // https://github.com/uBlockOrigin/uBlock-issues/issues/1547 @@ -523,6 +518,9 @@ while ( µb.alarmQueue.length !== 0 ) { case 'createSelfie': µb.selfieManager.create(); break; + case 'saveLocalSettings': + µb.saveLocalSettings(); + break; } } diff --git a/src/js/storage.js b/src/js/storage.js index 6ac3866324bc9..0c545e938a62b 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -97,23 +97,51 @@ import { /******************************************************************************/ { - let localSettingsLastSaved = Date.now(); - - const shouldSave = ( ) => { - if ( µb.localSettingsLastModified > localSettingsLastSaved ) { - µb.saveLocalSettings(); + µb.loadLocalSettings = ( ) => Promise.all([ + vAPI.sessionStorage.get('requestStats'), + vAPI.storage.get('requestStats'), + vAPI.storage.get([ 'blockedRequestCount', 'allowedRequestCount' ]), + ]).then(([ a, b, c ]) => { + if ( a instanceof Object && a.requestStats ) { return a.requestStats; } + if ( b instanceof Object && b.requestStats ) { return b.requestStats; } + if ( c instanceof Object && Object.keys(c).length === 2 ) { + return { + blockedCount: c.blockedRequestCount, + allowedCount: c.allowedRequestCount, + }; } - saveTimer.on(saveDelay); - }; + return { blockedCount: 0, allowedCount: 0 }; + }).then(({ blockedCount, allowedCount }) => { + µb.requestStats.blockedCount += blockedCount; + µb.requestStats.allowedCount += allowedCount; + }); - const saveTimer = vAPI.defer.create(shouldSave); - const saveDelay = { sec: 23 }; + const SAVE_DELAY_IN_MINUTES = 3.6; + const QUICK_SAVE_DELAY_IN_SECONDS = 23; + + const saveTimer = vAPI.defer.create(( ) => { + µb.saveLocalSettings(); + }); - saveTimer.onidle(saveDelay); + const quickSaveTimer = vAPI.defer.create(( ) => { + saveTimer.on({ min: SAVE_DELAY_IN_MINUTES }); + if ( vAPI.sessionStorage.unavailable ) { return; } + vAPI.sessionStorage.set({ requestStats: µb.requestStats }); + vAPI.alarms.createIfNotPresent('saveLocalSettings', { + delayInMinutes: SAVE_DELAY_IN_MINUTES + 0.5 + }); + }); + + µb.incrementRequestStats = (blocked, allowed) => { + µb.requestStats.blockedCount += blocked; + µb.requestStats.allowedCount += allowed; + quickSaveTimer.on({ sec: QUICK_SAVE_DELAY_IN_SECONDS }); + }; - µb.saveLocalSettings = function() { - localSettingsLastSaved = Date.now(); - return vAPI.storage.set(this.localSettings); + µb.saveLocalSettings = ( ) => { + vAPI.alarms.clear('saveLocalSettings'); + quickSaveTimer.off(); saveTimer.off(); + return vAPI.storage.set({ requestStats: µb.requestStats }); }; } From 275a1299e1838bd9f542aeab58a1f03b7e0aa1e7 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 11:54:51 -0500 Subject: [PATCH 040/180] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 130990cf641b6..2ba3cf2d8739b 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.56.1.5 +1.56.1.6 From 2a2764387446f4f5b0c6bdff24c1d37c909c8a28 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 11:56:49 -0500 Subject: [PATCH 041/180] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05650368c9c4f..04dcf5db6c1fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Fixes / changes +- [First lookup matching stock lists when importing URLs](https://github.com/gorhill/uBlock/commit/2b16a10b82) +- [Reset filter lists in worker when creating filters via "Block element"](https://github.com/gorhill/uBlock/commit/b0067b79d5) - [Remove trusted-source requirement when using `badfilter`](https://github.com/gorhill/uBlock/commit/3c299b8632) - [Redesign cache storage](https://github.com/gorhill/uBlock/commit/086766a924) - [Don't match network filter-derived regexes against non-network URIs](https://github.com/gorhill/uBlock/commit/2262a129ec) From 727e71b328ea2cbc531feb461ff2e2e354a2752a Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 12:05:40 -0500 Subject: [PATCH 042/180] Make Firefox dev build auto-update --- dist/firefox/updates.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index bbdfba7426bde..71a525ec528b6 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,9 +3,9 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.56.1.5", + "version": "1.56.1.6", "browser_specific_settings": { "gecko": { "strict_min_version": "78.0" } }, - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b5/uBlock0_1.56.1b5.firefox.signed.xpi" + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b6/uBlock0_1.56.1b6.firefox.signed.xpi" } ] } From d8544dc047d59f521c019d927de038cafff50a20 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 13:18:33 -0500 Subject: [PATCH 043/180] Forgot to declarare "alarms" permmission in manifest for Chromium Related commit: https://github.com/gorhill/uBlock/commit/059e4e5e28 --- platform/chromium/manifest.json | 1 + platform/opera/manifest.json | 1 + 2 files changed, 2 insertions(+) diff --git a/platform/chromium/manifest.json b/platform/chromium/manifest.json index 352dc57c5ea35..df686a0e77a51 100644 --- a/platform/chromium/manifest.json +++ b/platform/chromium/manifest.json @@ -97,6 +97,7 @@ "open_in_tab": true }, "permissions": [ + "alarms", "contextMenus", "privacy", "storage", diff --git a/platform/opera/manifest.json b/platform/opera/manifest.json index ea4dc7cf73246..afaaad33b7dbd 100644 --- a/platform/opera/manifest.json +++ b/platform/opera/manifest.json @@ -93,6 +93,7 @@ "name": "uBlock Origin", "options_page": "dashboard.html", "permissions": [ + "alarms", "contextMenus", "privacy", "storage", From 801d6500b04f31663fad496c3dc8d61337dfc6ff Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 13:28:04 -0500 Subject: [PATCH 044/180] Fix idbStorage.keys() Related commit: https://github.com/gorhill/uBlock/commit/d4efaf020b --- src/js/assets.js | 2 +- src/js/cachestorage.js | 7 ++++--- src/js/start.js | 3 +-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/js/assets.js b/src/js/assets.js index 749fb7802aa56..0eb8ef3c4b5e6 100644 --- a/src/js/assets.js +++ b/src/js/assets.js @@ -790,7 +790,7 @@ async function assetCacheRemove(pattern, options = {}) { } if ( options.janitor && pattern instanceof RegExp ) { const re = new RegExp( - pattern.source.replace(/^\^/, 'cache\/'), + pattern.source.replace(/^\^/, '^cache\\/'), pattern.flags ); const keys = await cacheStorage.keys(re); diff --git a/src/js/cachestorage.js b/src/js/cachestorage.js index f1e940df76471..f8b406c95a3e2 100644 --- a/src/js/cachestorage.js +++ b/src/js/cachestorage.js @@ -546,7 +546,7 @@ const idbStorage = (( ) => { }); }; - const getAllKeys = async function() { + const getAllKeys = async function(regex) { const db = await getDb(); if ( db === null ) { return []; } return new Promise(resolve => { @@ -562,6 +562,7 @@ const idbStorage = (( ) => { req.onsuccess = ev => { const cursor = ev.target && ev.target.result; if ( !cursor ) { return; } + if ( regex && regex.test(cursor.key) === false ) { return; } keys.push(cursor.key); cursor.continue(); }; @@ -685,8 +686,8 @@ const idbStorage = (( ) => { return setEntries(bin); }, - keys() { - return getAllKeys(); + keys(...args) { + return getAllKeys(...args); }, remove(...args) { diff --git a/src/js/start.js b/src/js/start.js index 2bda75c1fd81c..a3052cc08b45b 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -232,8 +232,7 @@ const onUserSettingsReady = fetched => { fetched.importedLists.length === 0 && fetched.externalLists !== '' ) { - fetched.importedLists = - fetched.externalLists.trim().split(/[\n\r]+/); + fetched.importedLists = fetched.externalLists.trim().split(/[\n\r]+/); } fromFetch(µb.userSettings, fetched); From 70cf53067d9601b8cac7ec31b51cb8aebd300fef Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 13:29:26 -0500 Subject: [PATCH 045/180] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 2ba3cf2d8739b..8f1e7bc5f646e 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.56.1.6 +1.56.1.7 From e0971fe0acd02fda526b12459428900b6beddcc4 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 13:36:16 -0500 Subject: [PATCH 046/180] Make Firefox dev build auto-update --- dist/firefox/updates.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index 71a525ec528b6..c8d4ff5c5e840 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,9 +3,9 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.56.1.6", + "version": "1.56.1.7", "browser_specific_settings": { "gecko": { "strict_min_version": "78.0" } }, - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b6/uBlock0_1.56.1b6.firefox.signed.xpi" + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b7/uBlock0_1.56.1b7.firefox.signed.xpi" } ] } From e02ea69c86a5a5b6501de7a4889bc4ccfb2f0564 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 19:53:06 -0500 Subject: [PATCH 047/180] Add advanced setting `requestStatsDisabled` To disable collating global blocked/allowed counts. Boolean, default to `false`. Setting to `true` will prevent uBO from loading/saving global blocked/allowed counts, and in such case the "Blocked since install" count instead reflects the count since uBO launched. Setting back to `false` will cause the counts to resume from last time they were saved. Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/3100 --- src/js/background.js | 1 + src/js/storage.js | 76 ++++++++++++++++++++++++++++++-------------- 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/js/background.js b/src/js/background.js index 601851924059c..d5f8f33bf0a4d 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -83,6 +83,7 @@ const hiddenSettingsDefault = { popupPanelLockedSections: 0, popupPanelHeightMode: 0, requestJournalProcessPeriod: 1000, + requestStatsDisabled: false, selfieAfter: 2, strictBlockingBypassDuration: 120, toolbarWarningTimeout: 60, diff --git a/src/js/storage.js b/src/js/storage.js index 0c545e938a62b..40cd4fca5f0c9 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -97,52 +97,80 @@ import { /******************************************************************************/ { - µb.loadLocalSettings = ( ) => Promise.all([ - vAPI.sessionStorage.get('requestStats'), - vAPI.storage.get('requestStats'), - vAPI.storage.get([ 'blockedRequestCount', 'allowedRequestCount' ]), - ]).then(([ a, b, c ]) => { - if ( a instanceof Object && a.requestStats ) { return a.requestStats; } - if ( b instanceof Object && b.requestStats ) { return b.requestStats; } - if ( c instanceof Object && Object.keys(c).length === 2 ) { - return { - blockedCount: c.blockedRequestCount, - allowedCount: c.allowedRequestCount, - }; - } - return { blockedCount: 0, allowedCount: 0 }; - }).then(({ blockedCount, allowedCount }) => { - µb.requestStats.blockedCount += blockedCount; - µb.requestStats.allowedCount += allowedCount; - }); + const requestStats = µb.requestStats; + let requestStatsDisabled = false; + + µb.loadLocalSettings = async ( ) => { + requestStatsDisabled = µb.hiddenSettings.requestStatsDisabled; + if ( requestStatsDisabled ) { return; } + return Promise.all([ + vAPI.sessionStorage.get('requestStats'), + vAPI.storage.get('requestStats'), + vAPI.storage.get([ 'blockedRequestCount', 'allowedRequestCount' ]), + ]).then(([ a, b, c ]) => { + if ( a instanceof Object && a.requestStats ) { return a.requestStats; } + if ( b instanceof Object && b.requestStats ) { return b.requestStats; } + if ( c instanceof Object && Object.keys(c).length === 2 ) { + return { + blockedCount: c.blockedRequestCount, + allowedCount: c.allowedRequestCount, + }; + } + return { blockedCount: 0, allowedCount: 0 }; + }).then(({ blockedCount, allowedCount }) => { + requestStats.blockedCount += blockedCount; + requestStats.allowedCount += allowedCount; + }); + }; const SAVE_DELAY_IN_MINUTES = 3.6; const QUICK_SAVE_DELAY_IN_SECONDS = 23; + const stopTimers = ( ) => { + vAPI.alarms.clear('saveLocalSettings'); + quickSaveTimer.off(); + saveTimer.off(); + }; + const saveTimer = vAPI.defer.create(( ) => { µb.saveLocalSettings(); }); const quickSaveTimer = vAPI.defer.create(( ) => { + if ( vAPI.sessionStorage.unavailable !== true ) { + vAPI.sessionStorage.set({ requestStats: requestStats }); + } + if ( requestStatsDisabled ) { return; } saveTimer.on({ min: SAVE_DELAY_IN_MINUTES }); - if ( vAPI.sessionStorage.unavailable ) { return; } - vAPI.sessionStorage.set({ requestStats: µb.requestStats }); vAPI.alarms.createIfNotPresent('saveLocalSettings', { delayInMinutes: SAVE_DELAY_IN_MINUTES + 0.5 }); }); µb.incrementRequestStats = (blocked, allowed) => { - µb.requestStats.blockedCount += blocked; - µb.requestStats.allowedCount += allowed; + requestStats.blockedCount += blocked; + requestStats.allowedCount += allowed; quickSaveTimer.on({ sec: QUICK_SAVE_DELAY_IN_SECONDS }); }; µb.saveLocalSettings = ( ) => { - vAPI.alarms.clear('saveLocalSettings'); - quickSaveTimer.off(); saveTimer.off(); + stopTimers(); + if ( requestStatsDisabled ) { return; } return vAPI.storage.set({ requestStats: µb.requestStats }); }; + + onBroadcast(msg => { + if ( msg.what !== 'hiddenSettingsChanged' ) { return; } + const newState = µb.hiddenSettings.requestStatsDisabled; + if ( requestStatsDisabled === newState ) { return; } + requestStatsDisabled = newState; + if ( newState ) { + stopTimers(); + µb.requestStats.blockedCount = µb.requestStats.allowedCount = 0; + } else { + µb.loadLocalSettings(); + } + }); } /******************************************************************************/ From 9b60a68a75744742f53e3a99d1aa89123544f7b2 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 19:59:39 -0500 Subject: [PATCH 048/180] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04dcf5db6c1fd..a2389ac162bb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Fixes / changes +- [Add advanced setting `requestStatsDisabled`](https://github.com/gorhill/uBlock/commit/e02ea69c86) - [First lookup matching stock lists when importing URLs](https://github.com/gorhill/uBlock/commit/2b16a10b82) - [Reset filter lists in worker when creating filters via "Block element"](https://github.com/gorhill/uBlock/commit/b0067b79d5) - [Remove trusted-source requirement when using `badfilter`](https://github.com/gorhill/uBlock/commit/3c299b8632) From a2ad1a67a9fd2aa9caa3d02657b35a6d4857627b Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 20:00:16 -0500 Subject: [PATCH 049/180] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 8f1e7bc5f646e..88790be515634 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.56.1.7 +1.56.1.8 From d1fe02328137ff9b6f68ec5d85f06512fa7ad38b Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 20:05:38 -0500 Subject: [PATCH 050/180] Make Firefox dev build auto-update --- dist/firefox/updates.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index c8d4ff5c5e840..ecf9a29eed60b 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,9 +3,9 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.56.1.7", + "version": "1.56.1.8", "browser_specific_settings": { "gecko": { "strict_min_version": "78.0" } }, - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b7/uBlock0_1.56.1b7.firefox.signed.xpi" + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b8/uBlock0_1.56.1b8.firefox.signed.xpi" } ] } From c2c80be8976a7f7687733acca341776d4be21a15 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 20:29:36 -0500 Subject: [PATCH 051/180] Forgot Chromium's `alarms` API is not promisified... --- platform/chromium/webext.js | 9 +++++++++ platform/common/vapi-background.js | 10 +++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/platform/chromium/webext.js b/platform/chromium/webext.js index 851b653e26dbd..5c6f470171ce4 100644 --- a/platform/chromium/webext.js +++ b/platform/chromium/webext.js @@ -54,6 +54,15 @@ const promisify = function(thisArg, fnName) { }; const webext = { + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/alarms + alarms: { + clear: promisifyNoFail(chrome.alarms, 'clear'), + clearAll: promisifyNoFail(chrome.alarms, 'clearAll'), + create: promisifyNoFail(chrome.alarms, 'create'), + get: promisifyNoFail(chrome.alarms, 'get'), + getAll: promisifyNoFail(chrome.alarms, 'getAll'), + onAlarm: chrome.alarms.onAlarm, + }, // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/browserAction browserAction: { setBadgeBackgroundColor: promisifyNoFail(chrome.browserAction, 'setBadgeBackgroundColor'), diff --git a/platform/common/vapi-background.js b/platform/common/vapi-background.js index 7f336066f664d..85acc9a0564b3 100644 --- a/platform/common/vapi-background.js +++ b/platform/common/vapi-background.js @@ -1781,20 +1781,20 @@ vAPI.cloud = (( ) => { vAPI.alarms = { create(...args) { - browser.alarms.create(...args); + webext.alarms.create(...args); }, createIfNotPresent(name, ...args) { - browser.alarms.get(name).then(details => { + webext.alarms.get(name).then(details => { if ( details !== undefined ) { return; } - browser.alarms.create(name, ...args); + webext.alarms.create(name, ...args); }); }, async clear(...args) { - return browser.alarms.clear(...args); + return webext.alarms.clear(...args); }, onAlarm: { addListener(...args) { - browser.alarms.onAlarm.addListener(...args); + webext.alarms.onAlarm.addListener(...args); }, }, }; From bc8aba48ab541da3a95ceabec3cc28edcd038951 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 20:31:23 -0500 Subject: [PATCH 052/180] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 88790be515634..8a80b7f763913 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.56.1.8 +1.56.1.9 From 7b290e99abee31f2ed89a5f3362534b6c2532d31 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 29 Feb 2024 20:35:43 -0500 Subject: [PATCH 053/180] Make Firefox dev build auto-update --- dist/firefox/updates.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index ecf9a29eed60b..62163214487b5 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,9 +3,9 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.56.1.8", + "version": "1.56.1.9", "browser_specific_settings": { "gecko": { "strict_min_version": "78.0" } }, - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b8/uBlock0_1.56.1b8.firefox.signed.xpi" + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b9/uBlock0_1.56.1b9.firefox.signed.xpi" } ] } From 953c978d597f633e898104228b6a5fcf29e012cc Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Fri, 1 Mar 2024 11:55:18 -0500 Subject: [PATCH 054/180] Move dragbar to the top of element picker dialog Also fine-tuning CSS for small screen displays. Related feedback: https://github.com/uBlockOrigin/uBlock-discussions/discussions/871 --- src/css/epicker-ui.css | 36 ++++++++++---------- src/web_accessible_resources/epicker-ui.html | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/css/epicker-ui.css b/src/css/epicker-ui.css index d09e1eff9910f..45e546181cc86 100644 --- a/src/css/epicker-ui.css +++ b/src/css/epicker-ui.css @@ -17,24 +17,24 @@ html#ublock0-epicker, box-sizing: border-box; cursor: default; display: none; - max-height: calc(100vh - 4px); - max-width: 36rem; - min-width: 24rem; + flex-direction: column; + max-width: min(32rem, 100vw - 4px); + min-width: min(24rem, 100vw - 4px); overflow-y: auto; - padding: 4px; position: fixed; right: 2px; - width: calc(40% - 2px); -} -/* https://github.com/uBlockOrigin/uBlock-issues/discussions/2114 */ -#ublock0-epicker aside { - min-width: min(24rem, 100vw - 4px); + width: min(32rem, 100vw - 4px); } #ublock0-epicker.paused:not(.zap) aside { - display: block; + display: flex; } +#ublock0-epicker aside > *:not(:first-child) { + padding: 2px; +} + #ublock0-epicker #toolbar { display: flex; + justify-content: space-between; } #ublock0-epicker ul { margin: 0.25em 0 0 0; @@ -46,8 +46,8 @@ html#ublock0-epicker, #ublock0-epicker #move { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAFElEQVQI12NgwAfKy8v/M5ANYLoBshgEyQo6H9UAAAAASUVORK5CYII='); cursor: grab; - flex-grow: 1; - margin: 2px 4px; + height: 1.5rem; + margin-bottom: 2px; opacity: 0.8; } #ublock0-epicker aside.moving #move { @@ -70,8 +70,8 @@ html#ublock0-epicker, #ublock0-epicker section .codeMirrorContainer { border: none; box-sizing: border-box; - height: 8em; - max-height: 50vh; + height: 6em; + max-height: min(6em, 10vh); min-height: 1em; padding: 2px; width: 100%; @@ -174,12 +174,9 @@ html#ublock0-epicker, overflow: hidden; } #ublock0-epicker #candidateFilters { - max-height: 14em; + max-height: min(14em, 20vh); overflow-y: auto; } -#ublock0-epicker #candidateFilters > li:first-of-type { - margin-bottom: 0.5em; -} #ublock0-epicker .changeFilter > li > span:nth-of-type(1) { font-weight: bold; } @@ -187,6 +184,9 @@ html#ublock0-epicker, font-size: smaller; color: gray; } +#ublock0-epicker #candidateFilters [data-i18n] { + font-size: 90%; +} #ublock0-epicker #candidateFilters .changeFilter { list-style-type: none; margin: 0 0 0 1em; diff --git a/src/web_accessible_resources/epicker-ui.html b/src/web_accessible_resources/epicker-ui.html index bd92f50115e97..3794b759671f3 100644 --- a/src/web_accessible_resources/epicker-ui.html +++ b/src/web_accessible_resources/epicker-ui.html @@ -16,6 +16,7 @@

-
- -
-
- - - -
+ + +
    @@ -56,7 +51,7 @@
- + From fa3c1f7200e24655a2c104af7ef73f172ffab863 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 2 Mar 2024 16:59:51 -0500 Subject: [PATCH 067/180] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31d396882a5cc..7a5bd8327adc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ ## Fixes / changes +- [No longer disable generic cosmetic filters by default on mobile](https://github.com/gorhill/uBlock/commit/7a768e7b1a) +- [Improve `spoof-css` scriptlet](https://github.com/gorhill/uBlock/commit/603239970d) - [Make asset updater compatible with non-persistent background page](https://github.com/gorhill/uBlock/commit/96704f2fda) - [Move dragbar to the top of element picker dialog](https://github.com/gorhill/uBlock/commit/953c978d59) + - [Move "Quit" button to top bar in element picker](https://github.com/gorhill/uBlock/commit/6266c4718d) - [Add advanced setting `requestStatsDisabled`](https://github.com/gorhill/uBlock/commit/e02ea69c86) - [First lookup matching stock lists when importing URLs](https://github.com/gorhill/uBlock/commit/2b16a10b82) - [Reset filter lists in worker when creating filters via "Block element"](https://github.com/gorhill/uBlock/commit/b0067b79d5) From 48e1c8d9a8c746cb5802935605eb0f236a38bc98 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 2 Mar 2024 17:01:56 -0500 Subject: [PATCH 068/180] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 369c70856970c..6170e5043cda6 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.56.1.10 +1.56.1.11 From 41aab2a20eea3327d7ff51a9053bd9c9615a2f3a Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 2 Mar 2024 17:11:07 -0500 Subject: [PATCH 069/180] Make Firefox dev build auto-update --- dist/firefox/updates.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index ed561e0e0eda2..6aae29df07725 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,9 +3,9 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.56.1.10", + "version": "1.56.1.11", "browser_specific_settings": { "gecko": { "strict_min_version": "78.0" } }, - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b10/uBlock0_1.56.1b10.firefox.signed.xpi" + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b11/uBlock0_1.56.1b11.firefox.signed.xpi" } ] } From 7f68c62f232d4d5f6265007a5a7be623da27c24b Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 2 Mar 2024 18:02:43 -0500 Subject: [PATCH 070/180] Remove obsolete built-in trusted directives Related feedback: https://github.com/uBlockOrigin/uBlock-issues/issues/3101#issuecomment-1974922229 --- src/js/start.js | 57 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/src/js/start.js b/src/js/start.js index 516ca934d5fe3..46a052f4f01c1 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -63,6 +63,11 @@ import { /******************************************************************************/ +let lastVersionInt = 0; +let thisVersionInt = 0; + +/******************************************************************************/ + vAPI.app.onShutdown = ( ) => { staticFilteringReverseLookup.shutdown(); io.updateStop(); @@ -144,15 +149,15 @@ const initializeTabs = async ( ) => { // Abort suspending network requests when uBO is merely being installed. const onVersionReady = async lastVersion => { - if ( lastVersion === vAPI.app.version ) { return; } + lastVersionInt = vAPI.app.intFromVersion(lastVersion); + thisVersionInt = vAPI.app.intFromVersion(vAPI.app.version); + if ( thisVersionInt === lastVersionInt ) { return; } vAPI.storage.set({ version: vAPI.app.version, versionUpdateTime: Date.now(), }); - const lastVersionInt = vAPI.app.intFromVersion(lastVersion); - // Special case: first installation if ( lastVersionInt === 0 ) { vAPI.net.unsuspend({ all: true, discard: true }); @@ -173,11 +178,6 @@ const onVersionReady = async lastVersion => { /******************************************************************************/ -// https://github.com/chrisaljoudi/uBlock/issues/226 -// Whitelist in memory. -// Whitelist parser needs PSL to be ready. -// gorhill 2014-12-15: not anymore -// // https://github.com/uBlockOrigin/uBlock-issues/issues/1433 // Allow admins to add their own trusted-site directives. @@ -185,16 +185,38 @@ const onNetWhitelistReady = (netWhitelistRaw, adminExtra) => { if ( typeof netWhitelistRaw === 'string' ) { netWhitelistRaw = netWhitelistRaw.split('\n'); } + + // Remove now obsolete built-in trusted directives + if ( lastVersionInt !== thisVersionInt ) { + if ( lastVersionInt < vAPI.app.intFromVersion('1.56.1b12') ) { + const obsolete = [ + 'about-scheme', + 'chrome-scheme', + 'edge-scheme', + 'opera-scheme', + 'vivaldi-scheme', + 'wyciwyg-scheme', + ]; + for ( const directive of obsolete ) { + const i = netWhitelistRaw.findIndex(s => + s === directive || s === `# ${directive}` + ); + if ( i === -1 ) { continue; } + netWhitelistRaw.splice(i, 1); + } + } + } + // Append admin-controlled trusted-site directives - if ( - adminExtra instanceof Object && - Array.isArray(adminExtra.trustedSiteDirectives) - ) { - for ( const directive of adminExtra.trustedSiteDirectives ) { - µb.netWhitelistDefault.push(directive); - netWhitelistRaw.push(directive); + if ( adminExtra instanceof Object ) { + if ( Array.isArray(adminExtra.trustedSiteDirectives) ) { + for ( const directive of adminExtra.trustedSiteDirectives ) { + µb.netWhitelistDefault.push(directive); + netWhitelistRaw.push(directive); + } } } + µb.netWhitelist = µb.whitelistFromArray(netWhitelistRaw); µb.netWhitelistModifyTime = Date.now(); }; @@ -392,14 +414,13 @@ try { ); ubolog(`Backend storage for cache will be ${µb.supportStats.cacheBackend}`); - const lastVersion = await vAPI.storage.get(createDefaultProps()).then(async fetched => { + await vAPI.storage.get(createDefaultProps()).then(async fetched => { ubolog(`Version ready ${Date.now()-vAPI.T0} ms after launch`); await onVersionReady(fetched.version); return fetched; }).then(fetched => { ubolog(`First fetch ready ${Date.now()-vAPI.T0} ms after launch`); onFirstFetchReady(fetched, adminExtra); - return fetched.version; }); await Promise.all([ @@ -421,7 +442,7 @@ try { ]); // https://github.com/uBlockOrigin/uBlock-issues/issues/1547 - if ( lastVersion === '0.0.0.0' && vAPI.webextFlavor.soup.has('chromium') ) { + if ( lastVersionInt === 0 && vAPI.webextFlavor.soup.has('chromium') ) { vAPI.app.restart(); return; } From 43e0e15125b6993f23b02d76f4530a8c32ff56c4 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 3 Mar 2024 10:08:56 -0500 Subject: [PATCH 071/180] More fine-tuning of element picker visual/behavior - Group "Pick" and "Preview" - Set minimal button width - Auto-minimize when picking instead of fully hiding the dialog: this allows to quit while in picking mode --- src/css/epicker-ui.css | 24 ++++++++++---------- src/web_accessible_resources/epicker-ui.html | 6 +++-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/css/epicker-ui.css b/src/css/epicker-ui.css index 716018d29aa46..9e90f5e9081e0 100644 --- a/src/css/epicker-ui.css +++ b/src/css/epicker-ui.css @@ -22,15 +22,18 @@ html#ublock0-epicker, overflow-y: auto; position: fixed; width: min(32rem, 100vw - 4px); + z-index: 100; } -#ublock0-epicker.paused:not(.zap) aside { +#ublock0-epicker:not(.zap) aside { display: flex; } +#ublock0-epicker:not(.paused) aside, #ublock0-epicker.minimized aside { min-width: min(16rem, 100vw - 4px); overflow: hidden; width: min(16rem, 100vw - 4px); } +#ublock0-epicker:not(.paused) aside > *:not(#windowbar), #ublock0-epicker.minimized aside > *:not(#windowbar) { display: none; } @@ -42,6 +45,9 @@ html#ublock0-epicker, display: flex; justify-content: space-between; } +#ublock0-epicker #toolbar button { + min-width: 6em; + } #ublock0-epicker ul { margin: 0.25em 0 0 0; } @@ -203,15 +209,6 @@ html#ublock0-epicker, background-color: var(--surface-2); } -/** - https://github.com/gorhill/uBlock/issues/3449 - https://github.com/uBlockOrigin/uBlock-issues/issues/55 -**/ -#ublock0-epicker.paused aside { - visibility: visible; - z-index: 100; -} - #ublock0-epicker svg#sea { cursor: crosshair; box-sizing: border-box; @@ -269,14 +266,17 @@ html#ublock0-epicker, height: 2em; width: 2em; } +#ublock0-epicker:not(.paused) #windowbar #minimize { + display: none; +} #windowbar #quit:hover, #windowbar #minimize:hover { background-color: var(--surface-2) } -html.minimized #windowbar #minimize svg > path, +#ublock0-epicker.minimized #windowbar #minimize svg > path, #windowbar #minimize svg > rect { display: none; } -html.minimized #windowbar #minimize svg > rect { +#ublock0-epicker.minimized #windowbar #minimize svg > rect { display: initial; } diff --git a/src/web_accessible_resources/epicker-ui.html b/src/web_accessible_resources/epicker-ui.html index ce0fdd9e9799d..ef5e9d72e4176 100644 --- a/src/web_accessible_resources/epicker-ui.html +++ b/src/web_accessible_resources/epicker-ui.html @@ -35,8 +35,10 @@
- - +
+ + +
From 270040d4660da9f78e10669ee90e6e5bacce9efa Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 3 Mar 2024 13:46:29 -0500 Subject: [PATCH 072/180] Fix disabled state of new benchmark buttons --- src/js/devtools.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/js/devtools.js b/src/js/devtools.js index 7d793896502c8..0763b0be83b6c 100644 --- a/src/js/devtools.js +++ b/src/js/devtools.js @@ -187,6 +187,7 @@ vAPI.messaging.send('dashboard', { dom.attr(button, 'disabled', null); }); }); + dom.attr('#cfe-benchmark', 'disabled', null); dom.on('#cfe-benchmark', 'click', ev => { const button = ev.target; dom.attr(button, 'disabled', ''); @@ -197,6 +198,7 @@ vAPI.messaging.send('dashboard', { dom.attr(button, 'disabled', null); }); }); + dom.attr('#sfe-benchmark', 'disabled', null); dom.on('#sfe-benchmark', 'click', ev => { const button = ev.target; dom.attr(button, 'disabled', ''); From aac88ac7662fc3978d4eabf9392a58ec2c497010 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 3 Mar 2024 14:30:30 -0500 Subject: [PATCH 073/180] Import translation work from https://crowdin.com/project/ublock --- platform/mv3/description/webstore.be.txt | 2 +- .../mv3/extension/_locales/az/messages.json | 2 +- .../extension/_locales/br_FR/messages.json | 2 +- .../mv3/extension/_locales/cv/messages.json | 2 +- .../mv3/extension/_locales/cy/messages.json | 2 +- .../mv3/extension/_locales/eo/messages.json | 2 +- .../mv3/extension/_locales/fa/messages.json | 2 +- .../mv3/extension/_locales/fil/messages.json | 2 +- .../mv3/extension/_locales/fr/messages.json | 2 +- .../mv3/extension/_locales/gu/messages.json | 2 +- .../mv3/extension/_locales/id/messages.json | 2 +- .../mv3/extension/_locales/kk/messages.json | 2 +- .../mv3/extension/_locales/kn/messages.json | 2 +- .../mv3/extension/_locales/lt/messages.json | 2 +- .../mv3/extension/_locales/mk/messages.json | 2 +- .../mv3/extension/_locales/mr/messages.json | 2 +- .../mv3/extension/_locales/oc/messages.json | 2 +- .../extension/_locales/pt_PT/messages.json | 2 +- .../mv3/extension/_locales/ru/messages.json | 2 +- .../mv3/extension/_locales/si/messages.json | 2 +- .../mv3/extension/_locales/sl/messages.json | 2 +- .../mv3/extension/_locales/so/messages.json | 2 +- .../mv3/extension/_locales/sw/messages.json | 2 +- .../mv3/extension/_locales/ta/messages.json | 2 +- .../mv3/extension/_locales/th/messages.json | 2 +- .../mv3/extension/_locales/ur/messages.json | 2 +- src/_locales/be/messages.json | 4 +-- src/_locales/br_FR/messages.json | 26 +++++++++---------- 28 files changed, 41 insertions(+), 41 deletions(-) diff --git a/platform/mv3/description/webstore.be.txt b/platform/mv3/description/webstore.be.txt index e03fa801ee7f0..d5a2e9812c0df 100644 --- a/platform/mv3/description/webstore.be.txt +++ b/platform/mv3/description/webstore.be.txt @@ -27,4 +27,4 @@ Keep in mind this is still a work in progress, with these end goals: - No broad host permissions at install time -- extended permissions are granted explicitly by the user on a per-site basis. -- Entirely declarative for reliability and CPU/memory efficiency. +- Цалкам дэкларатыўная ацэнка надзейнасці і эфектыўнасці работы працэсара/памяці. diff --git a/platform/mv3/extension/_locales/az/messages.json b/platform/mv3/extension/_locales/az/messages.json index 59b7d81ed5e66..3063ca3e1b335 100644 --- a/platform/mv3/extension/_locales/az/messages.json +++ b/platform/mv3/extension/_locales/az/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/br_FR/messages.json b/platform/mv3/extension/_locales/br_FR/messages.json index 654a391b9d827..b66a826842f6c 100644 --- a/platform/mv3/extension/_locales/br_FR/messages.json +++ b/platform/mv3/extension/_locales/br_FR/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "Roll eus anvioù domanioù el-lec'h ne vo silañ ebet", + "message": "Roll anvioù domanioù ha ne vo ket silet.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/cv/messages.json b/platform/mv3/extension/_locales/cv/messages.json index 627857e3668f5..8f33b46e39a91 100644 --- a/platform/mv3/extension/_locales/cv/messages.json +++ b/platform/mv3/extension/_locales/cv/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/cy/messages.json b/platform/mv3/extension/_locales/cy/messages.json index 6ab3d03e4f22c..fc3bbf3959c23 100644 --- a/platform/mv3/extension/_locales/cy/messages.json +++ b/platform/mv3/extension/_locales/cy/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/eo/messages.json b/platform/mv3/extension/_locales/eo/messages.json index 9e86eea19604b..f5c8fd9bc4de8 100644 --- a/platform/mv3/extension/_locales/eo/messages.json +++ b/platform/mv3/extension/_locales/eo/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/fa/messages.json b/platform/mv3/extension/_locales/fa/messages.json index b0ed662c46cf2..b3dc666bd95a5 100644 --- a/platform/mv3/extension/_locales/fa/messages.json +++ b/platform/mv3/extension/_locales/fa/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/fil/messages.json b/platform/mv3/extension/_locales/fil/messages.json index 68025d23f37dc..2364dc0159868 100644 --- a/platform/mv3/extension/_locales/fil/messages.json +++ b/platform/mv3/extension/_locales/fil/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/fr/messages.json b/platform/mv3/extension/_locales/fr/messages.json index 2f6a837f1e058..95e183b031ebe 100644 --- a/platform/mv3/extension/_locales/fr/messages.json +++ b/platform/mv3/extension/_locales/fr/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "Liste des noms de domaine pour lesquels aucun filtrage n'aura lieu", + "message": "Liste des noms de domaine qu'uBO Lite ne devra pas filtrer", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/gu/messages.json b/platform/mv3/extension/_locales/gu/messages.json index 627857e3668f5..8f33b46e39a91 100644 --- a/platform/mv3/extension/_locales/gu/messages.json +++ b/platform/mv3/extension/_locales/gu/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/id/messages.json b/platform/mv3/extension/_locales/id/messages.json index e3c6f69d1ea20..2c1ab7dbcba30 100644 --- a/platform/mv3/extension/_locales/id/messages.json +++ b/platform/mv3/extension/_locales/id/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/kk/messages.json b/platform/mv3/extension/_locales/kk/messages.json index 627857e3668f5..8f33b46e39a91 100644 --- a/platform/mv3/extension/_locales/kk/messages.json +++ b/platform/mv3/extension/_locales/kk/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/kn/messages.json b/platform/mv3/extension/_locales/kn/messages.json index 1e19123758a4d..969b1e737f402 100644 --- a/platform/mv3/extension/_locales/kn/messages.json +++ b/platform/mv3/extension/_locales/kn/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/lt/messages.json b/platform/mv3/extension/_locales/lt/messages.json index 0377c08d1b8b0..0623164c678d2 100644 --- a/platform/mv3/extension/_locales/lt/messages.json +++ b/platform/mv3/extension/_locales/lt/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/mk/messages.json b/platform/mv3/extension/_locales/mk/messages.json index 0ef10c7174bfb..dc5ecda6094b3 100644 --- a/platform/mv3/extension/_locales/mk/messages.json +++ b/platform/mv3/extension/_locales/mk/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/mr/messages.json b/platform/mv3/extension/_locales/mr/messages.json index 627857e3668f5..8f33b46e39a91 100644 --- a/platform/mv3/extension/_locales/mr/messages.json +++ b/platform/mv3/extension/_locales/mr/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/oc/messages.json b/platform/mv3/extension/_locales/oc/messages.json index 627857e3668f5..8f33b46e39a91 100644 --- a/platform/mv3/extension/_locales/oc/messages.json +++ b/platform/mv3/extension/_locales/oc/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/pt_PT/messages.json b/platform/mv3/extension/_locales/pt_PT/messages.json index a210d25cfb796..6f1a3cb8ed417 100644 --- a/platform/mv3/extension/_locales/pt_PT/messages.json +++ b/platform/mv3/extension/_locales/pt_PT/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "Lista de nomes de anfitriões para os quais não será efetuada qualquer filtragem", + "message": "Lista de nomes de anfitriões para os quais não será efetuada qualquer filtragem.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/ru/messages.json b/platform/mv3/extension/_locales/ru/messages.json index 0104b7c1165ee..6f99efabc64fb 100644 --- a/platform/mv3/extension/_locales/ru/messages.json +++ b/platform/mv3/extension/_locales/ru/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "Список имён хостов, для которых не будет производиться фильтрация", + "message": "Список имён хостов, для которых не будет производиться фильтрация.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/si/messages.json b/platform/mv3/extension/_locales/si/messages.json index 627857e3668f5..8f33b46e39a91 100644 --- a/platform/mv3/extension/_locales/si/messages.json +++ b/platform/mv3/extension/_locales/si/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/sl/messages.json b/platform/mv3/extension/_locales/sl/messages.json index 627857e3668f5..8f33b46e39a91 100644 --- a/platform/mv3/extension/_locales/sl/messages.json +++ b/platform/mv3/extension/_locales/sl/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/so/messages.json b/platform/mv3/extension/_locales/so/messages.json index 627857e3668f5..8f33b46e39a91 100644 --- a/platform/mv3/extension/_locales/so/messages.json +++ b/platform/mv3/extension/_locales/so/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/sw/messages.json b/platform/mv3/extension/_locales/sw/messages.json index 627857e3668f5..8f33b46e39a91 100644 --- a/platform/mv3/extension/_locales/sw/messages.json +++ b/platform/mv3/extension/_locales/sw/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/ta/messages.json b/platform/mv3/extension/_locales/ta/messages.json index 627857e3668f5..8f33b46e39a91 100644 --- a/platform/mv3/extension/_locales/ta/messages.json +++ b/platform/mv3/extension/_locales/ta/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/th/messages.json b/platform/mv3/extension/_locales/th/messages.json index 0a8e80734916f..baab9ce469923 100644 --- a/platform/mv3/extension/_locales/th/messages.json +++ b/platform/mv3/extension/_locales/th/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/platform/mv3/extension/_locales/ur/messages.json b/platform/mv3/extension/_locales/ur/messages.json index e66c4a94f4880..9544b84dc8ab3 100644 --- a/platform/mv3/extension/_locales/ur/messages.json +++ b/platform/mv3/extension/_locales/ur/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "List of hostnames for which no filtering will take place", + "message": "List of hostnames for which no filtering will take place.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/src/_locales/be/messages.json b/src/_locales/be/messages.json index dd83edaaf4dc8..d041cd0ecdcd6 100644 --- a/src/_locales/be/messages.json +++ b/src/_locales/be/messages.json @@ -68,7 +68,7 @@ "description": "Title for the advanced settings page" }, "popupPowerSwitchInfo": { - "message": "Націск: адключыць/уключыць uBlock₀ для гэтага сайта.\n\nCtrl+націск: адключыць uBlock₀ толькі на гэтай старонцы.", + "message": "Націсканне: адключыць/уключыць uBlock₀ для гэтага сайта.\n\nCtrl+націсканне: адключыць uBlock₀ толькі на гэтай старонцы.", "description": "English: Click: disable/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page." }, "popupPowerSwitchInfo1": { @@ -972,7 +972,7 @@ "description": "Label for widget to select type of issue" }, "supportS6Select1Option0": { - "message": "-- Выберыце катэгорыю --", + "message": "-- Выберыце запіс праблемы --", "description": "An entry in the widget used to select the type of issue" }, "supportS6Select1Option1": { diff --git a/src/_locales/br_FR/messages.json b/src/_locales/br_FR/messages.json index d46a4c103ff9f..d718593f10149 100644 --- a/src/_locales/br_FR/messages.json +++ b/src/_locales/br_FR/messages.json @@ -76,7 +76,7 @@ "description": "Message to be read by screen readers" }, "popupPowerSwitchInfo2": { - "message": "Klikit evit enaouiñ uBlock₀ war al lec'hienn-mañ.", + "message": "Enaouiñ uBlock₀ war al lec'hienn-mañ.", "description": "Message to be read by screen readers" }, "popupBlockedRequestPrompt": { @@ -136,11 +136,11 @@ "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups1": { - "message": "Klikit evit stankañ an holl brenestroù pop-up war al lec'hienn-mañ", + "message": "Stankañ an holl brenestroù pop-up war al lec'hienn-mañ", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups2": { - "message": "Klikit evit aotren an holl brenestroù pop-up war al lec'hienn-mañ", + "message": "Aotren an holl brenestroù pop-up war al lec'hienn-mañ", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoLargeMedia": { @@ -148,11 +148,11 @@ "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia1": { - "message": "Klikit evit stankañ an elfennoù media pounner war al lec'hienn-mañ", + "message": "Stankañ an elfennoù media pounner war al lec'hienn-mañ", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia2": { - "message": "Klikit evit aotren an elfennoù media pounner en-dro war al lec'hienn-mañ", + "message": "Aotren an elfennoù media pounner en-dro war al lec'hienn-mañ", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoCosmeticFiltering": { @@ -160,11 +160,11 @@ "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering1": { - "message": "Klikit evit lazhañ ar silañ kenedel war al lec'hienn-mañ", + "message": "Lazhañ ar silañ kenedel war al lec'hienn-mañ", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering2": { - "message": "Klikit evit enaouiñ ar silañ kenedel war al lec'hienn-mañ", + "message": "Enaouiñ ar silañ kenedel war al lec'hienn-mañ", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoRemoteFonts": { @@ -172,19 +172,19 @@ "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts1": { - "message": "Klikit evit stankañ an nodrezhioù diavaez war al lec'hienn-mañ", + "message": "Stankañ an nodrezhioù diavaez war al lec'hienn-mañ", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts2": { - "message": "Klikit evit aotren an nodrezhioù diavaez war al lec'hienn-mañ", + "message": "Aotren an nodrezhioù diavaez war al lec'hienn-mañ", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoScripting1": { - "message": "Klikit evit diweredekaat JavaScript war al lec'hienn-mañ", + "message": "Diweredekaat JavaScript war al lec'hienn-mañ", "description": "Tooltip for the no-scripting per-site switch" }, "popupTipNoScripting2": { - "message": "Klikit evit gweredekaat JavaScript war al lec'hienn-mañ en-dro", + "message": "Gweredekaat JavaScript war al lec'hienn-mañ en-dro", "description": "Tooltip for the no-scripting per-site switch" }, "popupNoPopups_v2": { @@ -224,11 +224,11 @@ "description": "Tooltip when hovering the top-most cell of the local-rules column." }, "popupTipSaveRules": { - "message": "Klikit evit lakaat ho kemmoù da dalvezout.", + "message": "Lakaat ho kemmoù da dalvezout.", "description": "Tooltip when hovering over the padlock in the dynamic filtering pane." }, "popupTipRevertRules": { - "message": "Klikit evit nullañ ar c'hemmoù ho peus graet.", + "message": "Nullañ ar c'hemmoù ho peus graet.", "description": "Tooltip when hovering over the eraser in the dynamic filtering pane." }, "popupAnyRulePrompt": { From 21a76e32a1f13fffd134e6909028a8f95c7e8198 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 3 Mar 2024 15:29:38 -0500 Subject: [PATCH 074/180] Add "Social widgets", "Cookie notices" sections in "Filter lists" pane Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/3154 --- assets/assets.dev.json | 28 ++++++++++++++-------------- src/_locales/en/messages.json | 8 ++++++++ src/js/3p-filters.js | 11 +++++------ 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/assets/assets.dev.json b/assets/assets.dev.json index 9cb6a3089e3ef..b327bce2197cb 100644 --- a/assets/assets.dev.json +++ b/assets/assets.dev.json @@ -246,10 +246,10 @@ }, "adguard-social": { "content": "filters", - "group": "annoyances", - "parent": "AdGuard – Annoyances", + "group": "social", + "parent": null, "off": true, - "title": "AdGuard – Social Media", + "title": "AdGuard – Social Widgets", "tags": "annoyances social", "contentURL": "https://filters.adtidy.org/extension/ublock/filters/4.txt", "supportURL": "https://github.com/AdguardTeam/AdguardFilters#adguard-filters", @@ -257,8 +257,8 @@ }, "adguard-cookies": { "content": "filters", - "group": "annoyances", - "parent": "AdGuard – Annoyances|AdGuard/uBO – Cookie Notices", + "group": "cookies", + "parent": "AdGuard/uBO – Cookie Notices", "off": true, "title": "AdGuard – Cookie Notices", "tags": "annoyances cookies", @@ -268,8 +268,8 @@ }, "ublock-cookies-adguard": { "content": "filters", - "group": "annoyances", - "parent": "AdGuard – Annoyances|AdGuard/uBO – Cookie Notices", + "group": "cookies", + "parent": "AdGuard/uBO – Cookie Notices", "off": true, "title": "uBlock filters – Cookie Notices", "tags": "annoyances cookies", @@ -328,7 +328,7 @@ }, "fanboy-thirdparty_social": { "content": "filters", - "group": "annoyances", + "group": "social", "off": true, "title": "Fanboy – Anti-Facebook", "tags": "privacy", @@ -369,8 +369,8 @@ }, "fanboy-cookiemonster": { "content": "filters", - "group": "annoyances", - "parent": "EasyList – Annoyances|EasyList/uBO – Cookie Notices", + "group": "cookies", + "parent": "EasyList/uBO – Cookie Notices", "off": true, "title": "EasyList – Cookie Notices", "tags": "annoyances cookies", @@ -389,8 +389,8 @@ }, "ublock-cookies-easylist": { "content": "filters", - "group": "annoyances", - "parent": "EasyList – Annoyances|EasyList/uBO – Cookie Notices", + "group": "cookies", + "parent": "EasyList/uBO – Cookie Notices", "off": true, "title": "uBlock filters – Cookie Notices", "tags": "annoyances cookies", @@ -441,8 +441,8 @@ }, "fanboy-social": { "content": "filters", - "group": "annoyances", - "parent": "EasyList – Annoyances", + "group": "social", + "parent": null, "off": true, "title": "EasyList – Social Widgets", "tags": "annoyances social", diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index e49503c25b7a4..36cd223a1c18c 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -483,6 +483,14 @@ "message": "Malware protection, security", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js index 6050e02761dcd..fb88a0958fc70 100644 --- a/src/js/3p-filters.js +++ b/src/js/3p-filters.js @@ -74,7 +74,9 @@ const renderNodeStats = (used, total) => { }; const i18nGroupName = name => { - return i18n$('3pGroup' + name.charAt(0).toUpperCase() + name.slice(1)); + const groupname = i18n$('3pGroup' + name.charAt(0).toUpperCase() + name.slice(1)); + if ( groupname !== '' ) { return groupname; } + return `${name.charAt(0).toLocaleUpperCase}${name.slice(1)}`; }; /******************************************************************************/ @@ -223,6 +225,8 @@ const renderFilterLists = ( ) => { 'privacy', 'malware', 'multipurpose', + 'cookies', + 'social', 'annoyances', 'regions', 'custom' @@ -235,9 +239,6 @@ const renderFilterLists = ( ) => { } for ( const [ listkey, listDetails ] of Object.entries(response.available) ) { let groupKey = listDetails.group; - if ( groupKey === 'social' ) { - groupKey = 'annoyances'; - } const groupDetails = listTree[groupKey]; if ( listDetails.parent !== undefined ) { let lists = groupDetails.lists; @@ -699,8 +700,6 @@ dom.on('.searchbar input', 'input', searchFilterLists); const expandedListSet = new Set([ 'uBlock filters', - 'AdGuard – Annoyances', - 'EasyList – Annoyances', ]); const listIsExpanded = which => { From 8781ffe82ac2dee4ffe0e4b9c2302bcbf2d9fcf5 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 3 Mar 2024 15:31:41 -0500 Subject: [PATCH 075/180] Minor CSS fine-tuning --- src/css/epicker-ui.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/css/epicker-ui.css b/src/css/epicker-ui.css index 9e90f5e9081e0..6620e71790082 100644 --- a/src/css/epicker-ui.css +++ b/src/css/epicker-ui.css @@ -46,7 +46,7 @@ html#ublock0-epicker, justify-content: space-between; } #ublock0-epicker #toolbar button { - min-width: 6em; + min-width: 5em; } #ublock0-epicker ul { margin: 0.25em 0 0 0; From 393fb91325770a9f2e52b96dd7c2fd688e709516 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 3 Mar 2024 15:32:37 -0500 Subject: [PATCH 076/180] Import translation work from https://crowdin.com/project/ublock --- src/_locales/ar/messages.json | 8 ++++++++ src/_locales/az/messages.json | 8 ++++++++ src/_locales/be/messages.json | 8 ++++++++ src/_locales/bg/messages.json | 8 ++++++++ src/_locales/bn/messages.json | 8 ++++++++ src/_locales/br_FR/messages.json | 8 ++++++++ src/_locales/bs/messages.json | 8 ++++++++ src/_locales/ca/messages.json | 8 ++++++++ src/_locales/cs/messages.json | 8 ++++++++ src/_locales/cv/messages.json | 8 ++++++++ src/_locales/cy/messages.json | 8 ++++++++ src/_locales/da/messages.json | 8 ++++++++ src/_locales/de/messages.json | 8 ++++++++ src/_locales/el/messages.json | 8 ++++++++ src/_locales/en_GB/messages.json | 8 ++++++++ src/_locales/eo/messages.json | 8 ++++++++ src/_locales/es/messages.json | 8 ++++++++ src/_locales/et/messages.json | 8 ++++++++ src/_locales/eu/messages.json | 8 ++++++++ src/_locales/fa/messages.json | 8 ++++++++ src/_locales/fi/messages.json | 8 ++++++++ src/_locales/fil/messages.json | 8 ++++++++ src/_locales/fr/messages.json | 8 ++++++++ src/_locales/fy/messages.json | 8 ++++++++ src/_locales/gl/messages.json | 8 ++++++++ src/_locales/gu/messages.json | 8 ++++++++ src/_locales/he/messages.json | 8 ++++++++ src/_locales/hi/messages.json | 8 ++++++++ src/_locales/hr/messages.json | 8 ++++++++ src/_locales/hu/messages.json | 8 ++++++++ src/_locales/hy/messages.json | 8 ++++++++ src/_locales/id/messages.json | 8 ++++++++ src/_locales/it/messages.json | 8 ++++++++ src/_locales/ja/messages.json | 8 ++++++++ src/_locales/ka/messages.json | 8 ++++++++ src/_locales/kk/messages.json | 8 ++++++++ src/_locales/kn/messages.json | 8 ++++++++ src/_locales/ko/messages.json | 8 ++++++++ src/_locales/lt/messages.json | 8 ++++++++ src/_locales/lv/messages.json | 8 ++++++++ src/_locales/mk/messages.json | 8 ++++++++ src/_locales/ml/messages.json | 8 ++++++++ src/_locales/mr/messages.json | 8 ++++++++ src/_locales/ms/messages.json | 8 ++++++++ src/_locales/nb/messages.json | 8 ++++++++ src/_locales/nl/messages.json | 8 ++++++++ src/_locales/oc/messages.json | 8 ++++++++ src/_locales/pa/messages.json | 8 ++++++++ src/_locales/pl/messages.json | 8 ++++++++ src/_locales/pt_BR/messages.json | 8 ++++++++ src/_locales/pt_PT/messages.json | 8 ++++++++ src/_locales/ro/messages.json | 8 ++++++++ src/_locales/ru/messages.json | 8 ++++++++ src/_locales/si/messages.json | 8 ++++++++ src/_locales/sk/messages.json | 8 ++++++++ src/_locales/sl/messages.json | 8 ++++++++ src/_locales/so/messages.json | 8 ++++++++ src/_locales/sq/messages.json | 8 ++++++++ src/_locales/sr/messages.json | 8 ++++++++ src/_locales/sv/messages.json | 8 ++++++++ src/_locales/sw/messages.json | 8 ++++++++ src/_locales/ta/messages.json | 8 ++++++++ src/_locales/te/messages.json | 8 ++++++++ src/_locales/th/messages.json | 8 ++++++++ src/_locales/tr/messages.json | 8 ++++++++ src/_locales/uk/messages.json | 8 ++++++++ src/_locales/ur/messages.json | 8 ++++++++ src/_locales/vi/messages.json | 8 ++++++++ src/_locales/zh_CN/messages.json | 8 ++++++++ src/_locales/zh_TW/messages.json | 8 ++++++++ 70 files changed, 560 insertions(+) diff --git a/src/_locales/ar/messages.json b/src/_locales/ar/messages.json index c589192f155c5..96f51cfa43baf 100644 --- a/src/_locales/ar/messages.json +++ b/src/_locales/ar/messages.json @@ -483,6 +483,14 @@ "message": "مواقع مصابة أو تحتوي على فايروسات", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "عناصر مزعجة", "description": "Filter lists section name" diff --git a/src/_locales/az/messages.json b/src/_locales/az/messages.json index ed47f71e349c0..85eafedd9c446 100644 --- a/src/_locales/az/messages.json +++ b/src/_locales/az/messages.json @@ -483,6 +483,14 @@ "message": "Zərərli domenlər", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Zəhlətökən elementlər əleyhinə filtrlər", "description": "Filter lists section name" diff --git a/src/_locales/be/messages.json b/src/_locales/be/messages.json index d041cd0ecdcd6..d80319f46b641 100644 --- a/src/_locales/be/messages.json +++ b/src/_locales/be/messages.json @@ -483,6 +483,14 @@ "message": "Дамены шкодных праграм", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Надакучлівасці", "description": "Filter lists section name" diff --git a/src/_locales/bg/messages.json b/src/_locales/bg/messages.json index ba1349d44e371..e995570a6c2ea 100644 --- a/src/_locales/bg/messages.json +++ b/src/_locales/bg/messages.json @@ -483,6 +483,14 @@ "message": "Защита от зловреден софтуер, сигурност", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Досадни неща", "description": "Filter lists section name" diff --git a/src/_locales/bn/messages.json b/src/_locales/bn/messages.json index e8d98b78e8a77..04293e459b6bb 100644 --- a/src/_locales/bn/messages.json +++ b/src/_locales/bn/messages.json @@ -483,6 +483,14 @@ "message": "ম্যালওয়্যার ডোমেইন", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "বিরক্তিকর", "description": "Filter lists section name" diff --git a/src/_locales/br_FR/messages.json b/src/_locales/br_FR/messages.json index d718593f10149..e62ddcf83acc5 100644 --- a/src/_locales/br_FR/messages.json +++ b/src/_locales/br_FR/messages.json @@ -483,6 +483,14 @@ "message": "Gwarez a-enep ar malware ha surentez", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Saotradurioù", "description": "Filter lists section name" diff --git a/src/_locales/bs/messages.json b/src/_locales/bs/messages.json index df1bbeb18bb82..1791678e68d0c 100644 --- a/src/_locales/bs/messages.json +++ b/src/_locales/bs/messages.json @@ -483,6 +483,14 @@ "message": "Zlonamjerne domene", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Smetnje", "description": "Filter lists section name" diff --git a/src/_locales/ca/messages.json b/src/_locales/ca/messages.json index c9b96fdadad3b..ff395ddcdb7e7 100644 --- a/src/_locales/ca/messages.json +++ b/src/_locales/ca/messages.json @@ -483,6 +483,14 @@ "message": "Dominis perillosos", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Elements molestos", "description": "Filter lists section name" diff --git a/src/_locales/cs/messages.json b/src/_locales/cs/messages.json index 6d01f756622ad..4e998a2e91f0d 100644 --- a/src/_locales/cs/messages.json +++ b/src/_locales/cs/messages.json @@ -483,6 +483,14 @@ "message": "Ochrana před malwarem, bezpečnost", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Dotěrnosti", "description": "Filter lists section name" diff --git a/src/_locales/cv/messages.json b/src/_locales/cv/messages.json index a509dc52bbfcd..2954ac8354456 100644 --- a/src/_locales/cv/messages.json +++ b/src/_locales/cv/messages.json @@ -483,6 +483,14 @@ "message": "Malware protection, security", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" diff --git a/src/_locales/cy/messages.json b/src/_locales/cy/messages.json index 3857f19ad3571..55932e1a33f18 100644 --- a/src/_locales/cy/messages.json +++ b/src/_locales/cy/messages.json @@ -483,6 +483,14 @@ "message": "Diogelwch ac amddiffyn rhag maleiswedd", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Pethau diflas", "description": "Filter lists section name" diff --git a/src/_locales/da/messages.json b/src/_locales/da/messages.json index 518dd8af6dba8..8201cc7379f10 100644 --- a/src/_locales/da/messages.json +++ b/src/_locales/da/messages.json @@ -483,6 +483,14 @@ "message": "Malware-beskyttelse, sikkerhed", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Gener", "description": "Filter lists section name" diff --git a/src/_locales/de/messages.json b/src/_locales/de/messages.json index 4dca050855083..01e33155ea43b 100644 --- a/src/_locales/de/messages.json +++ b/src/_locales/de/messages.json @@ -483,6 +483,14 @@ "message": "Schutz vor Schadsoftware, Sicherheit", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Belästigungen", "description": "Filter lists section name" diff --git a/src/_locales/el/messages.json b/src/_locales/el/messages.json index 12d758dbd9b8e..7e9baf6edddb7 100644 --- a/src/_locales/el/messages.json +++ b/src/_locales/el/messages.json @@ -483,6 +483,14 @@ "message": "Τομείς κακόβουλου λογισμικού", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Ενοχλήσεις", "description": "Filter lists section name" diff --git a/src/_locales/en_GB/messages.json b/src/_locales/en_GB/messages.json index e256888e105f0..8532f345b5464 100644 --- a/src/_locales/en_GB/messages.json +++ b/src/_locales/en_GB/messages.json @@ -483,6 +483,14 @@ "message": "Malware domains", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" diff --git a/src/_locales/eo/messages.json b/src/_locales/eo/messages.json index d784fb8d38d27..39139ed981b5d 100644 --- a/src/_locales/eo/messages.json +++ b/src/_locales/eo/messages.json @@ -483,6 +483,14 @@ "message": "Domajno kun fiprogramaro", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Ĝenoj", "description": "Filter lists section name" diff --git a/src/_locales/es/messages.json b/src/_locales/es/messages.json index 02fdc7df99e22..c5eead0fe371e 100644 --- a/src/_locales/es/messages.json +++ b/src/_locales/es/messages.json @@ -483,6 +483,14 @@ "message": "Protección de malware, seguridad", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Elementos molestos", "description": "Filter lists section name" diff --git a/src/_locales/et/messages.json b/src/_locales/et/messages.json index 0a5610b12766a..2a907dd366691 100644 --- a/src/_locales/et/messages.json +++ b/src/_locales/et/messages.json @@ -483,6 +483,14 @@ "message": "Pahavara kaitse, turvalisus", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Tüütused", "description": "Filter lists section name" diff --git a/src/_locales/eu/messages.json b/src/_locales/eu/messages.json index 1dba3a0be3d19..0b625b68a88d7 100644 --- a/src/_locales/eu/messages.json +++ b/src/_locales/eu/messages.json @@ -483,6 +483,14 @@ "message": "Malware domeinuak", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Eragozpenak", "description": "Filter lists section name" diff --git a/src/_locales/fa/messages.json b/src/_locales/fa/messages.json index 9fb421958fb01..95cef962189b7 100644 --- a/src/_locales/fa/messages.json +++ b/src/_locales/fa/messages.json @@ -483,6 +483,14 @@ "message": "دامنه های مخرب", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "مزاحم‌ها", "description": "Filter lists section name" diff --git a/src/_locales/fi/messages.json b/src/_locales/fi/messages.json index 1578f98fb25a8..9d3c94eb0733b 100644 --- a/src/_locales/fi/messages.json +++ b/src/_locales/fi/messages.json @@ -483,6 +483,14 @@ "message": "Haittaohjelmasuojaus, tietoturva", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Ärsykkeet", "description": "Filter lists section name" diff --git a/src/_locales/fil/messages.json b/src/_locales/fil/messages.json index 7a4dbc1e95032..d8f8933a8eb5e 100644 --- a/src/_locales/fil/messages.json +++ b/src/_locales/fil/messages.json @@ -483,6 +483,14 @@ "message": "Mga domain na may malware", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Mga nakakaabalang bagay", "description": "Filter lists section name" diff --git a/src/_locales/fr/messages.json b/src/_locales/fr/messages.json index eb0a8804caece..bd0ee6f939e2d 100644 --- a/src/_locales/fr/messages.json +++ b/src/_locales/fr/messages.json @@ -483,6 +483,14 @@ "message": "Protection anti-malware et sécurité", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Nuisances", "description": "Filter lists section name" diff --git a/src/_locales/fy/messages.json b/src/_locales/fy/messages.json index 0b5caa39ab412..22f681b88aef3 100644 --- a/src/_locales/fy/messages.json +++ b/src/_locales/fy/messages.json @@ -483,6 +483,14 @@ "message": "Malwaredomeinen", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Ungeriif", "description": "Filter lists section name" diff --git a/src/_locales/gl/messages.json b/src/_locales/gl/messages.json index fcd25fea4f557..a4d522e94d553 100644 --- a/src/_locales/gl/messages.json +++ b/src/_locales/gl/messages.json @@ -483,6 +483,14 @@ "message": "Dominios de malware", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Molestias", "description": "Filter lists section name" diff --git a/src/_locales/gu/messages.json b/src/_locales/gu/messages.json index c7ec09bcb631a..5c4e2a810d627 100644 --- a/src/_locales/gu/messages.json +++ b/src/_locales/gu/messages.json @@ -483,6 +483,14 @@ "message": "Malware protection, security", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" diff --git a/src/_locales/he/messages.json b/src/_locales/he/messages.json index 166dde6fe7b03..fc1a63588ff07 100644 --- a/src/_locales/he/messages.json +++ b/src/_locales/he/messages.json @@ -483,6 +483,14 @@ "message": "הגנה מפני נוזקות, אבטחה", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "מטרדים", "description": "Filter lists section name" diff --git a/src/_locales/hi/messages.json b/src/_locales/hi/messages.json index 9c7114e2d5ac9..6b9de1cf69ff4 100644 --- a/src/_locales/hi/messages.json +++ b/src/_locales/hi/messages.json @@ -483,6 +483,14 @@ "message": "मैलवेयर डोमेन", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "सतानेवाले विज्ञापन", "description": "Filter lists section name" diff --git a/src/_locales/hr/messages.json b/src/_locales/hr/messages.json index 16cd14dce1130..f880e5115c74a 100644 --- a/src/_locales/hr/messages.json +++ b/src/_locales/hr/messages.json @@ -483,6 +483,14 @@ "message": "Zaštita od zlonamjernog softvera, sigurnost", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Nametljivost", "description": "Filter lists section name" diff --git a/src/_locales/hu/messages.json b/src/_locales/hu/messages.json index f2a8c92e3f953..3087ed3f10913 100644 --- a/src/_locales/hu/messages.json +++ b/src/_locales/hu/messages.json @@ -483,6 +483,14 @@ "message": "Malware domainek", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Kellemetlenségek", "description": "Filter lists section name" diff --git a/src/_locales/hy/messages.json b/src/_locales/hy/messages.json index 12b9366640cd5..24aef58eb67de 100644 --- a/src/_locales/hy/messages.json +++ b/src/_locales/hy/messages.json @@ -483,6 +483,14 @@ "message": "Վնասակար տիրույթներ", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Ջղայնացնող տարրեր", "description": "Filter lists section name" diff --git a/src/_locales/id/messages.json b/src/_locales/id/messages.json index ef26caf039f03..2024f78dd239e 100644 --- a/src/_locales/id/messages.json +++ b/src/_locales/id/messages.json @@ -483,6 +483,14 @@ "message": "Perlindungan malware, keamanan", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Gangguan", "description": "Filter lists section name" diff --git a/src/_locales/it/messages.json b/src/_locales/it/messages.json index e21ef03c3833f..0a2c9d97d6a63 100644 --- a/src/_locales/it/messages.json +++ b/src/_locales/it/messages.json @@ -483,6 +483,14 @@ "message": "Domini con Malware", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Elementi fastidiosi", "description": "Filter lists section name" diff --git a/src/_locales/ja/messages.json b/src/_locales/ja/messages.json index efa13a1e05a4a..e13391739890d 100644 --- a/src/_locales/ja/messages.json +++ b/src/_locales/ja/messages.json @@ -483,6 +483,14 @@ "message": "マルウェアドメイン", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "迷惑系", "description": "Filter lists section name" diff --git a/src/_locales/ka/messages.json b/src/_locales/ka/messages.json index 778afaf26d487..0f1daffb8f967 100644 --- a/src/_locales/ka/messages.json +++ b/src/_locales/ka/messages.json @@ -483,6 +483,14 @@ "message": "მავნე დომენები", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "შემაწუხებელი შიგთავსი", "description": "Filter lists section name" diff --git a/src/_locales/kk/messages.json b/src/_locales/kk/messages.json index 11bfdf12b3296..ee63cd5d1da4a 100644 --- a/src/_locales/kk/messages.json +++ b/src/_locales/kk/messages.json @@ -483,6 +483,14 @@ "message": "Malware protection, security", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" diff --git a/src/_locales/kn/messages.json b/src/_locales/kn/messages.json index 271bfb652ad5d..b01d313bd1a4d 100644 --- a/src/_locales/kn/messages.json +++ b/src/_locales/kn/messages.json @@ -483,6 +483,14 @@ "message": "Malware protection, security", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" diff --git a/src/_locales/ko/messages.json b/src/_locales/ko/messages.json index f5c5d4d646353..a8bc25e190cbe 100644 --- a/src/_locales/ko/messages.json +++ b/src/_locales/ko/messages.json @@ -483,6 +483,14 @@ "message": "멀웨어 도메인", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "골칫거리", "description": "Filter lists section name" diff --git a/src/_locales/lt/messages.json b/src/_locales/lt/messages.json index 4bfb04f49c7e0..a0218ba583c73 100644 --- a/src/_locales/lt/messages.json +++ b/src/_locales/lt/messages.json @@ -483,6 +483,14 @@ "message": "Kenksmingos sritys", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Erzinimas", "description": "Filter lists section name" diff --git a/src/_locales/lv/messages.json b/src/_locales/lv/messages.json index 0bc85407f1966..54cc8633fe128 100644 --- a/src/_locales/lv/messages.json +++ b/src/_locales/lv/messages.json @@ -483,6 +483,14 @@ "message": "Ļaundabīgo programmu domēni", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Traucējoši elementi", "description": "Filter lists section name" diff --git a/src/_locales/mk/messages.json b/src/_locales/mk/messages.json index b21f5d9bc9f66..52fc6422894f7 100644 --- a/src/_locales/mk/messages.json +++ b/src/_locales/mk/messages.json @@ -483,6 +483,14 @@ "message": "Домени на малвер", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Досадни", "description": "Filter lists section name" diff --git a/src/_locales/ml/messages.json b/src/_locales/ml/messages.json index 78a64f45beefb..768d4fb225fe7 100644 --- a/src/_locales/ml/messages.json +++ b/src/_locales/ml/messages.json @@ -483,6 +483,14 @@ "message": "മാല്‍വെയര്‍ ഡൊമൈനുകള്‍", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "ശല്യപ്പെടുത്തലുകൾ", "description": "Filter lists section name" diff --git a/src/_locales/mr/messages.json b/src/_locales/mr/messages.json index efea77d44e556..273a6a938e563 100644 --- a/src/_locales/mr/messages.json +++ b/src/_locales/mr/messages.json @@ -483,6 +483,14 @@ "message": "मालवेअर डोमेन", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "चिडवणाऱ्या गोष्टी", "description": "Filter lists section name" diff --git a/src/_locales/ms/messages.json b/src/_locales/ms/messages.json index cd95689d848d6..196550feafbb4 100644 --- a/src/_locales/ms/messages.json +++ b/src/_locales/ms/messages.json @@ -483,6 +483,14 @@ "message": "Domain perisian hasad", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Kejengkelan", "description": "Filter lists section name" diff --git a/src/_locales/nb/messages.json b/src/_locales/nb/messages.json index cb3cfb0feb8d4..0b4196b1a50b6 100644 --- a/src/_locales/nb/messages.json +++ b/src/_locales/nb/messages.json @@ -483,6 +483,14 @@ "message": "Beskyttelse mot skadelig programvare, sikkerhet", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Irritasjonsmomenter", "description": "Filter lists section name" diff --git a/src/_locales/nl/messages.json b/src/_locales/nl/messages.json index 24aeff97e2668..0ac86279f3ef0 100644 --- a/src/_locales/nl/messages.json +++ b/src/_locales/nl/messages.json @@ -483,6 +483,14 @@ "message": "Bescherming tegen malware, beveiliging", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Storende elementen", "description": "Filter lists section name" diff --git a/src/_locales/oc/messages.json b/src/_locales/oc/messages.json index e3367cf92f892..24cd451fa3ecf 100644 --- a/src/_locales/oc/messages.json +++ b/src/_locales/oc/messages.json @@ -483,6 +483,14 @@ "message": "Domenis malfasents", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" diff --git a/src/_locales/pa/messages.json b/src/_locales/pa/messages.json index ce82acd7d4630..64a71a56e745f 100644 --- a/src/_locales/pa/messages.json +++ b/src/_locales/pa/messages.json @@ -483,6 +483,14 @@ "message": "ਮਾਲਵੇਅਰਾਂ ਤੋਂ ਬਚਾਅ, ਸੁਰੱਖਿਆ", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "ਅਣਚਾਹੇ", "description": "Filter lists section name" diff --git a/src/_locales/pl/messages.json b/src/_locales/pl/messages.json index 22a18b8d282be..784b1786fb28a 100644 --- a/src/_locales/pl/messages.json +++ b/src/_locales/pl/messages.json @@ -483,6 +483,14 @@ "message": "Ochrona przed złośliwym oprogramowaniem, bezpieczeństwo", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Elementy irytujące", "description": "Filter lists section name" diff --git a/src/_locales/pt_BR/messages.json b/src/_locales/pt_BR/messages.json index 0c9e2b8dbd7ec..7c6ba0024dcd0 100644 --- a/src/_locales/pt_BR/messages.json +++ b/src/_locales/pt_BR/messages.json @@ -483,6 +483,14 @@ "message": "Proteção contra malware, segurança", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Aborrecimentos", "description": "Filter lists section name" diff --git a/src/_locales/pt_PT/messages.json b/src/_locales/pt_PT/messages.json index 0cbad9c9f53ad..20058902d8d6b 100644 --- a/src/_locales/pt_PT/messages.json +++ b/src/_locales/pt_PT/messages.json @@ -483,6 +483,14 @@ "message": "Proteção contra malware, segurança", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Inconveniências", "description": "Filter lists section name" diff --git a/src/_locales/ro/messages.json b/src/_locales/ro/messages.json index 2bf389ba6b919..991d07816076f 100644 --- a/src/_locales/ro/messages.json +++ b/src/_locales/ro/messages.json @@ -483,6 +483,14 @@ "message": "Domenii malițioase", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Neplăceri", "description": "Filter lists section name" diff --git a/src/_locales/ru/messages.json b/src/_locales/ru/messages.json index 244bf8bd3c036..97981b953a65a 100644 --- a/src/_locales/ru/messages.json +++ b/src/_locales/ru/messages.json @@ -483,6 +483,14 @@ "message": "Защита от вредоносных сайтов, безопасность", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Раздражающие элементы", "description": "Filter lists section name" diff --git a/src/_locales/si/messages.json b/src/_locales/si/messages.json index d4da659a30ca9..7eeea663fda0a 100644 --- a/src/_locales/si/messages.json +++ b/src/_locales/si/messages.json @@ -483,6 +483,14 @@ "message": "Malware protection, security", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" diff --git a/src/_locales/sk/messages.json b/src/_locales/sk/messages.json index 3a762de285081..3e108721a08a9 100644 --- a/src/_locales/sk/messages.json +++ b/src/_locales/sk/messages.json @@ -483,6 +483,14 @@ "message": "Domény malvéru", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Obťažujúce", "description": "Filter lists section name" diff --git a/src/_locales/sl/messages.json b/src/_locales/sl/messages.json index 98d208b8f05bb..4d8a4e9940ddb 100644 --- a/src/_locales/sl/messages.json +++ b/src/_locales/sl/messages.json @@ -483,6 +483,14 @@ "message": "Zlonamerne domene", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "\t\nNadlegovanje", "description": "Filter lists section name" diff --git a/src/_locales/so/messages.json b/src/_locales/so/messages.json index 939e15252f1d8..af0d4699e0f08 100644 --- a/src/_locales/so/messages.json +++ b/src/_locales/so/messages.json @@ -483,6 +483,14 @@ "message": "Malware protection, security", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Waxaa lagu talinayaa in lagu suurtageliyo doorashadan aaladaha awoodda yar.", "description": "Filter lists section name" diff --git a/src/_locales/sq/messages.json b/src/_locales/sq/messages.json index 13755779a8d09..a0fb18f9df3eb 100644 --- a/src/_locales/sq/messages.json +++ b/src/_locales/sq/messages.json @@ -483,6 +483,14 @@ "message": "Domenet e rrezikshme, siguria", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Elementet e bezdisshme", "description": "Filter lists section name" diff --git a/src/_locales/sr/messages.json b/src/_locales/sr/messages.json index 71e3978403831..2e2ff0fb76028 100644 --- a/src/_locales/sr/messages.json +++ b/src/_locales/sr/messages.json @@ -483,6 +483,14 @@ "message": "Заштита од злонамерног софтвера, безбедност", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Сметње", "description": "Filter lists section name" diff --git a/src/_locales/sv/messages.json b/src/_locales/sv/messages.json index 3af9d59cb8f89..56d1378572a93 100644 --- a/src/_locales/sv/messages.json +++ b/src/_locales/sv/messages.json @@ -483,6 +483,14 @@ "message": "Skydd mot skadlig programvara, säkerhet", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Störande", "description": "Filter lists section name" diff --git a/src/_locales/sw/messages.json b/src/_locales/sw/messages.json index 29dbc1a47000d..be9ebc0403b64 100644 --- a/src/_locales/sw/messages.json +++ b/src/_locales/sw/messages.json @@ -483,6 +483,14 @@ "message": "Vikoa vya programu hasidi", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Vikasirisho", "description": "Filter lists section name" diff --git a/src/_locales/ta/messages.json b/src/_locales/ta/messages.json index c278a57c26cc8..f483741d4ee7d 100644 --- a/src/_locales/ta/messages.json +++ b/src/_locales/ta/messages.json @@ -483,6 +483,14 @@ "message": "தீப்பொருள் ஆள்களங்கள்", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "எரிச்சல்கள்", "description": "Filter lists section name" diff --git a/src/_locales/te/messages.json b/src/_locales/te/messages.json index 6762d3c43444d..c6d969e15841e 100644 --- a/src/_locales/te/messages.json +++ b/src/_locales/te/messages.json @@ -483,6 +483,14 @@ "message": "మాల్వేర్ డొమైన్లు", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "అసౌకర్యాల పట్టిక", "description": "Filter lists section name" diff --git a/src/_locales/th/messages.json b/src/_locales/th/messages.json index d7679c9811e16..92b6341ce0414 100644 --- a/src/_locales/th/messages.json +++ b/src/_locales/th/messages.json @@ -483,6 +483,14 @@ "message": "Malware domains", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" diff --git a/src/_locales/tr/messages.json b/src/_locales/tr/messages.json index d4d98a4588cd2..b85b6ba07d71b 100644 --- a/src/_locales/tr/messages.json +++ b/src/_locales/tr/messages.json @@ -483,6 +483,14 @@ "message": "Zararlı alan adları", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Can sıkıcı ögeler", "description": "Filter lists section name" diff --git a/src/_locales/uk/messages.json b/src/_locales/uk/messages.json index 9546f4fa96664..008d3cbad0566 100644 --- a/src/_locales/uk/messages.json +++ b/src/_locales/uk/messages.json @@ -483,6 +483,14 @@ "message": "Домени шкідливих програм", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Надокучливості", "description": "Filter lists section name" diff --git a/src/_locales/ur/messages.json b/src/_locales/ur/messages.json index e262067aa5e42..2a7ab636bba96 100644 --- a/src/_locales/ur/messages.json +++ b/src/_locales/ur/messages.json @@ -483,6 +483,14 @@ "message": "Malware protection, security", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Annoyances", "description": "Filter lists section name" diff --git a/src/_locales/vi/messages.json b/src/_locales/vi/messages.json index 22a6945a5f696..467664439dc05 100644 --- a/src/_locales/vi/messages.json +++ b/src/_locales/vi/messages.json @@ -483,6 +483,14 @@ "message": "Bảo mật, bảo vệ khỏi phần mềm nguy hiểm", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "Phiền toái", "description": "Filter lists section name" diff --git a/src/_locales/zh_CN/messages.json b/src/_locales/zh_CN/messages.json index 4b4621b4d2019..924588e216357 100644 --- a/src/_locales/zh_CN/messages.json +++ b/src/_locales/zh_CN/messages.json @@ -483,6 +483,14 @@ "message": "恶意软件防护、安全", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "骚扰", "description": "Filter lists section name" diff --git a/src/_locales/zh_TW/messages.json b/src/_locales/zh_TW/messages.json index dd19a3cd6a4a5..3172cf6c559f2 100644 --- a/src/_locales/zh_TW/messages.json +++ b/src/_locales/zh_TW/messages.json @@ -483,6 +483,14 @@ "message": "惡意軟體防護及安全性", "description": "Filter lists section name" }, + "3pGroupSocial": { + "message": "Social widgets", + "description": "Filter lists section name" + }, + "3pGroupCookies": { + "message": "Cookie notices", + "description": "Filter lists section name" + }, "3pGroupAnnoyances": { "message": "嫌惡元素", "description": "Filter lists section name" From 0268980233e151debb195e7cc9df7a4bc4b28e9b Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 3 Mar 2024 15:33:56 -0500 Subject: [PATCH 077/180] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a5bd8327adc6..fdb042ed13d02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Fixes / changes +- [Add "Social widgets", "Cookie notices" sections in "Filter lists" pane](https://github.com/gorhill/uBlock/commit/21a76e32a1) - [No longer disable generic cosmetic filters by default on mobile](https://github.com/gorhill/uBlock/commit/7a768e7b1a) - [Improve `spoof-css` scriptlet](https://github.com/gorhill/uBlock/commit/603239970d) - [Make asset updater compatible with non-persistent background page](https://github.com/gorhill/uBlock/commit/96704f2fda) From d1ccbdfc7cf9da47bbaf3b918b0c0730fd8a866c Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 3 Mar 2024 15:34:28 -0500 Subject: [PATCH 078/180] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 6170e5043cda6..1b2757a426bfa 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.56.1.11 +1.56.1.12 From ca432fa5d2f29b060e21798b11bb08403f9d76f7 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 3 Mar 2024 15:40:49 -0500 Subject: [PATCH 079/180] Make Firefox dev build auto-update --- dist/firefox/updates.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index 6aae29df07725..51b5dfd2cf3ad 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,9 +3,9 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.56.1.11", + "version": "1.56.1.12", "browser_specific_settings": { "gecko": { "strict_min_version": "78.0" } }, - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b11/uBlock0_1.56.1b11.firefox.signed.xpi" + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b12/uBlock0_1.56.1b12.firefox.signed.xpi" } ] } From 0f4e50db07893fec965aa4e2328f20bf38be457c Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 3 Mar 2024 20:05:45 -0500 Subject: [PATCH 080/180] Remove sections with no lists in "Filter lists" pane Related feedback: https://github.com/uBlockOrigin/uBlock-issues/issues/3154#issuecomment-1975413427 --- src/js/3p-filters.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js index fb88a0958fc70..742bab7d43632 100644 --- a/src/js/3p-filters.js +++ b/src/js/3p-filters.js @@ -254,6 +254,14 @@ const renderFilterLists = ( ) => { groupDetails.lists[listkey] = listDetails; } } + // https://github.com/uBlockOrigin/uBlock-issues/issues/3154#issuecomment-1975413427 + // Remove empty sections + for ( const groupkey of groupKeys ) { + const lists = listTree[groupkey].lists; + if ( Object.keys(lists).length !== 0 ) { continue; } + delete listTree[groupkey]; + } + const listEntries = createListEntries('root', listTree); qs$('#lists .listEntries').replaceWith(listEntries); From a557f6211244af96b52cba7da60e2d60afac328b Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 3 Mar 2024 20:08:15 -0500 Subject: [PATCH 081/180] Support aborting "Pick" mode in element picker This allows a user to go back to the previous selection after entering interactive "Pick" mode. --- src/css/epicker-ui.css | 3 --- src/js/dom.js | 4 ++-- src/js/epicker-ui.js | 13 ++++++++++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/css/epicker-ui.css b/src/css/epicker-ui.css index 6620e71790082..01d3f05afeedc 100644 --- a/src/css/epicker-ui.css +++ b/src/css/epicker-ui.css @@ -266,9 +266,6 @@ html#ublock0-epicker, height: 2em; width: 2em; } -#ublock0-epicker:not(.paused) #windowbar #minimize { - display: none; -} #windowbar #quit:hover, #windowbar #minimize:hover { background-color: var(--surface-2) diff --git a/src/js/dom.js b/src/js/dom.js index 3d2f517a220eb..5c4d19479f80d 100644 --- a/src/js/dom.js +++ b/src/js/dom.js @@ -161,9 +161,9 @@ dom.cl = class { } } - static remove(target, name) { + static remove(target, ...names) { for ( const elem of normalizeTarget(target) ) { - elem.classList.remove(name); + elem.classList.remove(...names); } } diff --git a/src/js/epicker-ui.js b/src/js/epicker-ui.js index 00621718bc221..0c7ea1f5a63e3 100644 --- a/src/js/epicker-ui.js +++ b/src/js/epicker-ui.js @@ -802,14 +802,16 @@ const showDialog = function(details) { /******************************************************************************/ const pausePicker = function() { - pickerRoot.classList.add('paused'); + dom.cl.add(pickerRoot, 'paused'); + dom.cl.remove(pickerRoot, 'minimized'); svgListening(false); }; /******************************************************************************/ const unpausePicker = function() { - pickerRoot.classList.remove('paused', 'preview'); + dom.cl.remove(pickerRoot, 'paused', 'preview'); + dom.cl.add(pickerRoot, 'minimized'); pickerContentPort.postMessage({ what: 'togglePreview', state: false, @@ -836,7 +838,12 @@ const startPicker = function() { $id('create').addEventListener('click', onCreateClicked); $id('pick').addEventListener('click', onPickClicked); $id('minimize').addEventListener('click', ( ) => { - dom.cl.toggle(dom.html, 'minimized'); + if ( dom.cl.has(pickerRoot, 'paused') === false ) { + pausePicker(); + onCandidateChanged(); + } else { + dom.cl.toggle(pickerRoot, 'minimized'); + } }); $id('quit').addEventListener('click', onQuitClicked); $id('move').addEventListener('mousedown', onStartMoving); From e2ed86ff65c7295c51be4a7255b16b19b890be20 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 3 Mar 2024 20:19:24 -0500 Subject: [PATCH 082/180] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 1b2757a426bfa..349f76fff13e5 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.56.1.12 +1.56.1.13 From 0a72745f9cf5aba45553fb55eed4859fa581f044 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 3 Mar 2024 20:28:07 -0500 Subject: [PATCH 083/180] Import translation work from https://crowdin.com/project/ublock --- platform/mv3/extension/_locales/hr/messages.json | 2 +- src/_locales/ar/messages.json | 2 +- src/_locales/be/messages.json | 4 ++-- src/_locales/bg/messages.json | 4 ++-- src/_locales/cs/messages.json | 4 ++-- src/_locales/da/messages.json | 4 ++-- src/_locales/es/messages.json | 4 ++-- src/_locales/et/messages.json | 4 ++-- src/_locales/fi/messages.json | 4 ++-- src/_locales/fr/messages.json | 4 ++-- src/_locales/hr/messages.json | 4 ++-- src/_locales/hu/messages.json | 4 ++-- src/_locales/it/messages.json | 4 ++-- src/_locales/nl/messages.json | 4 ++-- src/_locales/pl/messages.json | 4 ++-- src/_locales/pt_BR/messages.json | 4 ++-- src/_locales/sr/messages.json | 4 ++-- 17 files changed, 32 insertions(+), 32 deletions(-) diff --git a/platform/mv3/extension/_locales/hr/messages.json b/platform/mv3/extension/_locales/hr/messages.json index 14c94018f2f6e..a41a576e1d1ee 100644 --- a/platform/mv3/extension/_locales/hr/messages.json +++ b/platform/mv3/extension/_locales/hr/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "Popis naziva hostova za koje se neće izvršiti filtriranje", + "message": "Popis naziva hostova za koje se neće izvršiti filtriranje.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/src/_locales/ar/messages.json b/src/_locales/ar/messages.json index 96f51cfa43baf..d506782f1bac8 100644 --- a/src/_locales/ar/messages.json +++ b/src/_locales/ar/messages.json @@ -488,7 +488,7 @@ "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "اشعارات ملفات تعريف الارتباط", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/be/messages.json b/src/_locales/be/messages.json index d80319f46b641..a609d6cf2f63b 100644 --- a/src/_locales/be/messages.json +++ b/src/_locales/be/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Віджэты сацыяльных сетак", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Абвесткі пра кукі", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/bg/messages.json b/src/_locales/bg/messages.json index e995570a6c2ea..e2ca912bfafb2 100644 --- a/src/_locales/bg/messages.json +++ b/src/_locales/bg/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Социални джаджи", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Известия за бисквитки", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/cs/messages.json b/src/_locales/cs/messages.json index 4e998a2e91f0d..371f5a9d17a37 100644 --- a/src/_locales/cs/messages.json +++ b/src/_locales/cs/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Widgety sociálních sítí", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Oznámení o používání souborů cookie", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/da/messages.json b/src/_locales/da/messages.json index 8201cc7379f10..460643f0fb46b 100644 --- a/src/_locales/da/messages.json +++ b/src/_locales/da/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Sociale widgets", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Cookie-meddelelser", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/es/messages.json b/src/_locales/es/messages.json index c5eead0fe371e..7e804129454a8 100644 --- a/src/_locales/es/messages.json +++ b/src/_locales/es/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Artilugios sociales", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Avisos de cookies", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/et/messages.json b/src/_locales/et/messages.json index 2a907dd366691..cd1d21067f538 100644 --- a/src/_locales/et/messages.json +++ b/src/_locales/et/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Sotsiaalvõrgustike vidinad", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Küpsise teatised", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/fi/messages.json b/src/_locales/fi/messages.json index 9d3c94eb0733b..2353fb7da8ea8 100644 --- a/src/_locales/fi/messages.json +++ b/src/_locales/fi/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Sosiaaliset widgetit", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Evästeilmoitukset", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/fr/messages.json b/src/_locales/fr/messages.json index bd0ee6f939e2d..eabde13e49888 100644 --- a/src/_locales/fr/messages.json +++ b/src/_locales/fr/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Widgets de réseaux sociaux", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Bannières de cookie", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/hr/messages.json b/src/_locales/hr/messages.json index f880e5115c74a..766390af448ab 100644 --- a/src/_locales/hr/messages.json +++ b/src/_locales/hr/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Socijalni widgeti", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Obavijest o kolačićima", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/hu/messages.json b/src/_locales/hu/messages.json index 3087ed3f10913..296db18a45767 100644 --- a/src/_locales/hu/messages.json +++ b/src/_locales/hu/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Közösségi widgetek", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Cookie értesítések", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/it/messages.json b/src/_locales/it/messages.json index 0a2c9d97d6a63..ab7bab24b465e 100644 --- a/src/_locales/it/messages.json +++ b/src/_locales/it/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Widget sociali", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Avviso sui cookie", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/nl/messages.json b/src/_locales/nl/messages.json index 0ac86279f3ef0..23990b2b38297 100644 --- a/src/_locales/nl/messages.json +++ b/src/_locales/nl/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Sociale widgets", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Cookiemeldingen", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/pl/messages.json b/src/_locales/pl/messages.json index 784b1786fb28a..f4785918b1779 100644 --- a/src/_locales/pl/messages.json +++ b/src/_locales/pl/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Widżety społecznościowe", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Powiadomienia o ciasteczkach", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/pt_BR/messages.json b/src/_locales/pt_BR/messages.json index 7c6ba0024dcd0..14df025769bb4 100644 --- a/src/_locales/pt_BR/messages.json +++ b/src/_locales/pt_BR/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Widgets sociais", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Avisos dos cookies", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/sr/messages.json b/src/_locales/sr/messages.json index 2e2ff0fb76028..ebc6c3d3a2278 100644 --- a/src/_locales/sr/messages.json +++ b/src/_locales/sr/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Друштвени виџети", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Обавештења о колачићима", "description": "Filter lists section name" }, "3pGroupAnnoyances": { From 041e13304e332e8ea5eafcb29f69546184dae4de Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 3 Mar 2024 20:35:57 -0500 Subject: [PATCH 084/180] Make Firefox dev build auto-update --- dist/firefox/updates.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index 51b5dfd2cf3ad..a0909f2f3f4f5 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,9 +3,9 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.56.1.12", + "version": "1.56.1.13", "browser_specific_settings": { "gecko": { "strict_min_version": "78.0" } }, - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b12/uBlock0_1.56.1b12.firefox.signed.xpi" + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b13/uBlock0_1.56.1b13.firefox.signed.xpi" } ] } From c7a9bcb7b269574561e6ff35461877a4acdcd365 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 4 Mar 2024 11:38:55 -0500 Subject: [PATCH 085/180] Import translation work from https://crowdin.com/project/ublock --- platform/mv3/extension/_locales/sv/messages.json | 2 +- src/_locales/de/messages.json | 4 ++-- src/_locales/gl/messages.json | 4 ++-- src/_locales/he/messages.json | 4 ++-- src/_locales/hi/messages.json | 4 ++-- src/_locales/ja/messages.json | 4 ++-- src/_locales/lv/messages.json | 4 ++-- src/_locales/ru/messages.json | 4 ++-- src/_locales/sk/messages.json | 4 ++-- src/_locales/sv/messages.json | 4 ++-- src/_locales/th/messages.json | 14 +++++++------- src/_locales/uk/messages.json | 4 ++-- src/_locales/vi/messages.json | 4 ++-- src/_locales/zh_TW/messages.json | 4 ++-- 14 files changed, 32 insertions(+), 32 deletions(-) diff --git a/platform/mv3/extension/_locales/sv/messages.json b/platform/mv3/extension/_locales/sv/messages.json index fddcca552d045..c43bdc862ab17 100644 --- a/platform/mv3/extension/_locales/sv/messages.json +++ b/platform/mv3/extension/_locales/sv/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "Lista över värdnamn för vilka ingen filtrering kommer att äga rum", + "message": "Lista över värdnamn som inte kommer att filtreras", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/src/_locales/de/messages.json b/src/_locales/de/messages.json index 01e33155ea43b..e83756552d9ef 100644 --- a/src/_locales/de/messages.json +++ b/src/_locales/de/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Soziale Widgets", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Cookie-Hinweise", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/gl/messages.json b/src/_locales/gl/messages.json index a4d522e94d553..e33d494fc2cd5 100644 --- a/src/_locales/gl/messages.json +++ b/src/_locales/gl/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Widgets sociais", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Avisos de rastro", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/he/messages.json b/src/_locales/he/messages.json index fc1a63588ff07..6dec4b338b541 100644 --- a/src/_locales/he/messages.json +++ b/src/_locales/he/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "יישומונים חברתיים", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "הודעות בקשר לעוגיות", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/hi/messages.json b/src/_locales/hi/messages.json index 6b9de1cf69ff4..14a65d0b72e17 100644 --- a/src/_locales/hi/messages.json +++ b/src/_locales/hi/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "सोशल विज़ेट्स", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "कुकी सूचनाएं", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/ja/messages.json b/src/_locales/ja/messages.json index e13391739890d..1df0b80d8204f 100644 --- a/src/_locales/ja/messages.json +++ b/src/_locales/ja/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "SNS ウィジェット", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "クッキー通知", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/lv/messages.json b/src/_locales/lv/messages.json index 54cc8633fe128..83f04f452fcf9 100644 --- a/src/_locales/lv/messages.json +++ b/src/_locales/lv/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Sabiedriskās ekrānvadīklas", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Sīkdatņu paziņojumi", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/ru/messages.json b/src/_locales/ru/messages.json index 97981b953a65a..91f56398c590b 100644 --- a/src/_locales/ru/messages.json +++ b/src/_locales/ru/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Виджеты соцсетей", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Уведомления о файлах куки", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/sk/messages.json b/src/_locales/sk/messages.json index 3e108721a08a9..ef93e82577813 100644 --- a/src/_locales/sk/messages.json +++ b/src/_locales/sk/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Sociálne widgety", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Oznámenie o cookies", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/sv/messages.json b/src/_locales/sv/messages.json index 56d1378572a93..2456fe936f862 100644 --- a/src/_locales/sv/messages.json +++ b/src/_locales/sv/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Sociala widgets", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Cookie-meddelanden", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/th/messages.json b/src/_locales/th/messages.json index 92b6341ce0414..0ae8e04ecc527 100644 --- a/src/_locales/th/messages.json +++ b/src/_locales/th/messages.json @@ -588,11 +588,11 @@ "description": "Will discard manually-edited content and exit manual-edit mode" }, "rulesImport": { - "message": "Import from file…", + "message": "นำเข้าจากไฟล์", "description": "" }, "rulesExport": { - "message": "Export to file…", + "message": "ส่งออกไปยังไฟล์", "description": "Button in the 'My rules' pane" }, "rulesDefaultFileName": { @@ -612,7 +612,7 @@ "description": "English: label for sort option." }, "rulesSortByType": { - "message": "Rule type", + "message": "ประเภทกฎ", "description": "English: a sort option for list of rules." }, "rulesSortBySource": { @@ -620,15 +620,15 @@ "description": "English: a sort option for list of rules." }, "rulesSortByDestination": { - "message": "Destination", + "message": "ปลายทาง", "description": "English: a sort option for list of rules." }, "whitelistPrompt": { - "message": "The trusted site directives dictate on which web pages uBlock Origin should be disabled. One entry per line.", + "message": "ระบุไซต์ที่เชื่อถือได้เพื่อกำหนดว่าควรปิดการใช้งาน uBlock Origin ที่หน้าเว็บใด พิมพ์หนึ่งรายการต่อบรรทัด", "description": "A concise description of the 'Trusted sites' pane." }, "whitelistImport": { - "message": "Import and append…", + "message": "นำเข้าและรวม", "description": "Button in the 'Trusted sites' pane" }, "whitelistExport": { @@ -640,7 +640,7 @@ "description": "The default filename to use for import/export purpose" }, "whitelistApply": { - "message": "Apply changes", + "message": "ใช้การเปลี่ยนแปลง", "description": "English: Apply changes" }, "logRequestsHeaderType": { diff --git a/src/_locales/uk/messages.json b/src/_locales/uk/messages.json index 008d3cbad0566..726cf75d6ad39 100644 --- a/src/_locales/uk/messages.json +++ b/src/_locales/uk/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Віджети соціальних мереж", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Повідомлення про файли cookie", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/vi/messages.json b/src/_locales/vi/messages.json index 467664439dc05..950e28eb8a1ad 100644 --- a/src/_locales/vi/messages.json +++ b/src/_locales/vi/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Tiện ích xã hội", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Các thông báo cookie", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/zh_TW/messages.json b/src/_locales/zh_TW/messages.json index 3172cf6c559f2..b96f4e92895d1 100644 --- a/src/_locales/zh_TW/messages.json +++ b/src/_locales/zh_TW/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "社交小工具", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Cookie 通知", "description": "Filter lists section name" }, "3pGroupAnnoyances": { From 3682eed638c35f0a0ffddc2e33fbb5caa2c7b858 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 4 Mar 2024 21:52:34 -0500 Subject: [PATCH 086/180] Use "small viewport" for max height of dashboard Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/3058 --- src/css/dashboard.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/css/dashboard.css b/src/css/dashboard.css index ba02d974d0b3f..f9f6696fa721c 100644 --- a/src/css/dashboard.css +++ b/src/css/dashboard.css @@ -2,6 +2,7 @@ html, body { display: flex; flex-direction: column; height: 100vh; + height: 100svh; justify-content: stretch; overflow: hidden; position: relative; From 238724eed1abd464f7ea60c703f5b899d01478c8 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 4 Mar 2024 23:57:47 -0500 Subject: [PATCH 087/180] Fix looking-up unexisting sections in "Filter lists" pane Related feedback: https://old.reddit.com/r/uBlockOrigin/comments/1b6tad0/i_updated_to_the_new_dev_build_1561b10_and_now/ --- src/js/3p-filters.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js index 742bab7d43632..7ad5fadc11790 100644 --- a/src/js/3p-filters.js +++ b/src/js/3p-filters.js @@ -238,8 +238,9 @@ const renderFilterLists = ( ) => { }; } for ( const [ listkey, listDetails ] of Object.entries(response.available) ) { - let groupKey = listDetails.group; - const groupDetails = listTree[groupKey]; + const groupkey = listDetails.group; + const groupDetails = listTree[groupkey]; + if ( groupDetails === undefined ) { continue; } if ( listDetails.parent !== undefined ) { let lists = groupDetails.lists; for ( const parent of listDetails.parent.split('|') ) { @@ -257,8 +258,9 @@ const renderFilterLists = ( ) => { // https://github.com/uBlockOrigin/uBlock-issues/issues/3154#issuecomment-1975413427 // Remove empty sections for ( const groupkey of groupKeys ) { - const lists = listTree[groupkey].lists; - if ( Object.keys(lists).length !== 0 ) { continue; } + const groupDetails = listTree[groupkey]; + if ( groupDetails === undefined ) { continue; } + if ( Object.keys(groupDetails.lists).length !== 0 ) { continue; } delete listTree[groupkey]; } @@ -707,7 +709,8 @@ dom.on('.searchbar input', 'input', searchFilterLists); /******************************************************************************/ const expandedListSet = new Set([ - 'uBlock filters', + 'cookies', + 'social', ]); const listIsExpanded = which => { From 395fa7197b94b8fe34653cd3a49da4b043a17154 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 5 Mar 2024 07:27:02 -0500 Subject: [PATCH 088/180] Reduce TTL of serializer workers --- src/js/scuo-serializer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/scuo-serializer.js b/src/js/scuo-serializer.js index 82fbae1e05b41..04796f74ee52a 100644 --- a/src/js/scuo-serializer.js +++ b/src/js/scuo-serializer.js @@ -1115,7 +1115,7 @@ export const isCompressed = s => * */ const defaultConfig = { - threadTTL: 5000, + threadTTL: 3000, }; const validateConfig = { From 185ff3fd9682726bb1d473d19dc3eea1212d1e9d Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 5 Mar 2024 07:59:11 -0500 Subject: [PATCH 089/180] Empty target directory instead of re-creating it For better web-ext compatibility. --- tools/make-firefox.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/make-firefox.sh b/tools/make-firefox.sh index 2278c58fa58aa..fc5a1967804b2 100755 --- a/tools/make-firefox.sh +++ b/tools/make-firefox.sh @@ -8,8 +8,8 @@ echo "*** uBlock0.firefox: Creating web store package" BLDIR=dist/build DES="$BLDIR"/uBlock0.firefox -rm -rf $DES mkdir -p $DES +rm -rf $DES/* echo "*** uBlock0.firefox: Copying common files" bash ./tools/copy-common-files.sh $DES From 24d94e559d1560b0ce61c1652a3850d7ae7fef30 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 5 Mar 2024 08:00:42 -0500 Subject: [PATCH 090/180] Fix issue with "My filters" pane on mobile Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/3058 --- src/css/dashboard.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/css/dashboard.css b/src/css/dashboard.css index f9f6696fa721c..ee03ed179f1ae 100644 --- a/src/css/dashboard.css +++ b/src/css/dashboard.css @@ -2,7 +2,6 @@ html, body { display: flex; flex-direction: column; height: 100vh; - height: 100svh; justify-content: stretch; overflow: hidden; position: relative; @@ -107,6 +106,9 @@ body.noDashboard #dashboard-nav { } /* touch-screen devices */ +:root.mobile, :root.mobile body { + height: 100svh; + } :root.mobile #dashboard-nav { flex-wrap: nowrap; overflow-x: auto; From 04fead8d8a3be75e4e3ad3d7c5795d047adbbdce Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 5 Mar 2024 08:02:55 -0500 Subject: [PATCH 091/180] Group lists with unknown group to special section Related issue: https://old.reddit.com/r/uBlockOrigin/comments/1b6tad0 --- src/js/3p-filters.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js index 7ad5fadc11790..b37bc18e35383 100644 --- a/src/js/3p-filters.js +++ b/src/js/3p-filters.js @@ -229,6 +229,7 @@ const renderFilterLists = ( ) => { 'social', 'annoyances', 'regions', + 'unknown', 'custom' ]; for ( const key of groupKeys ) { @@ -238,9 +239,11 @@ const renderFilterLists = ( ) => { }; } for ( const [ listkey, listDetails ] of Object.entries(response.available) ) { - const groupkey = listDetails.group; + let groupkey = listDetails.group; + if ( listTree.hasOwnProperty(groupkey) === false ) { + groupkey = 'unknown'; + } const groupDetails = listTree[groupkey]; - if ( groupDetails === undefined ) { continue; } if ( listDetails.parent !== undefined ) { let lists = groupDetails.lists; for ( const parent of listDetails.parent.split('|') ) { From f18d62ee4cd1c1585b3d990781adecaeea1893fe Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 5 Mar 2024 08:11:39 -0500 Subject: [PATCH 092/180] Import translation work from https://crowdin.com/project/ublock --- platform/mv3/description/webstore.et.txt | 10 +++++----- src/_locales/fy/messages.json | 4 ++-- src/_locales/tr/messages.json | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/platform/mv3/description/webstore.et.txt b/platform/mv3/description/webstore.et.txt index d3bf2365d60c7..520e4ce134edc 100644 --- a/platform/mv3/description/webstore.et.txt +++ b/platform/mv3/description/webstore.et.txt @@ -17,14 +17,14 @@ Kuid uBOL võimaldab *selgesõnaliselt* anda täpsemaid lubasid teie valitud vee Veebilehele täpsustatud lubade andmiseks ava hüpikpaneel ja vali põhjalikum filtreerimisrežiim, näiteks Optimaalne või Põhjalik. -Seejärel hoiatab brauser teid praeguse saidi laienduse taotletud täiendavate õiguste andmise tagajärgede eest ja peate brauserile ütlema, kas nõustute taotlusega või keeldute sellest. +Seejärel hoiatab veebilehitseja praeguse veebilehe laienduse taotletud täiendavate õiguste andmise tagajärgede eest ja peate veebilehitsejale ütlema, kas nõustute taotlusega või keeldute sellest. -Kui nõustute uBOLi taotlusega täiendavate õiguste saamiseks praegusel saidil, saab see praeguse saidi sisu paremini filtreerida. +Kui nõustute uBOLi taotlusega täiendavate õiguste saamiseks praegusel veebilehel, saab see praeguse veebilehe sisu paremini filtreerida. -Vaikimisi filtreerimisrežiimi saate määrata uBOLi suvandite lehelt Kui määrate optimaalse või põhjaliku režiimi tavaliseks, peate andma uBOLile loa kõikide veebilehtede andmete lugemiseks ja muutmiseks. +Fltreerimise tavarežiimi saate määrata uBOLi valikute lehelt. Kui määrate optimaalse või põhjaliku režiimi tavaliseks, peate andma uBOLile loa kõikide veebilehtede andmete lugemiseks ja muutmiseks. -Pidage meeles, et see on veel pooleliolev töö, mille lõppeesmärgid on järgmised: +Pidage meeles, et see on veel arendamisel, mille lõppeesmärgid on järgmised: -- Installimise ajal laialdased hostiõigused puuduvad – kasutaja annab laiendatud load selgesõnaliselt saidipõhiselt. +- Paigaldamise ajal laialdased hostiõigused puuduvad – kasutaja annab laiendatud load selgesõnaliselt ja veebilehe põhiselt. - Täiesti deklaratiivne töökindluse ja protsessori/mälu tõhususe osas. diff --git a/src/_locales/fy/messages.json b/src/_locales/fy/messages.json index 22f681b88aef3..dbdb82b367ed8 100644 --- a/src/_locales/fy/messages.json +++ b/src/_locales/fy/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Sosjale widgets", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Cookiemeldingen", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/tr/messages.json b/src/_locales/tr/messages.json index b85b6ba07d71b..451dc6ab71ee7 100644 --- a/src/_locales/tr/messages.json +++ b/src/_locales/tr/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Sosyal gereçler", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Çerez bildirimleri", "description": "Filter lists section name" }, "3pGroupAnnoyances": { From b95a1e987f87fdc54bfbb2cd21e35409ccefc8a9 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 5 Mar 2024 09:03:51 -0500 Subject: [PATCH 093/180] Use hard tabs --- assets/assets.dev.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/assets.dev.json b/assets/assets.dev.json index b327bce2197cb..eb5fe4fba79c4 100644 --- a/assets/assets.dev.json +++ b/assets/assets.dev.json @@ -247,7 +247,7 @@ "adguard-social": { "content": "filters", "group": "social", - "parent": null, + "parent": null, "off": true, "title": "AdGuard – Social Widgets", "tags": "annoyances social", @@ -442,7 +442,7 @@ "fanboy-social": { "content": "filters", "group": "social", - "parent": null, + "parent": null, "off": true, "title": "EasyList – Social Widgets", "tags": "annoyances social", From 63acdcbdebad04d9af4c56ad7387d97575e7f5a3 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 5 Mar 2024 11:11:42 -0500 Subject: [PATCH 094/180] Assume UTF-8 when no encoding can be looked up. This will make HTML filtering and `replace=` filter option less likely to be bypassed by uBO, as the body response filterer previously required an encoding to be expressly declared before acting on the response body. UTF-8 usage is currently reported as ~98.2%: https://w3techs.com/technologies/history_overview/character_encoding --- src/js/traffic.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/js/traffic.js b/src/js/traffic.js index 02043ac1e2b64..cb56063f41e79 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -749,7 +749,7 @@ const bodyFilterer = (( ) => { /* t */ if ( bytes[i+6] !== 0x74 ) { continue; } break; } - if ( (i - 40) >= 65536 ) { return; } + if ( (i + 40) >= 65536 ) { return; } i += 8; // find first alpha character let j = -1; @@ -827,13 +827,17 @@ const bodyFilterer = (( ) => { } if ( this.status !== 'finishedtransferringdata' ) { return; } - // If encoding is still unknown, try to extract from stream data + // If encoding is still unknown, try to extract from stream data. + // Just assume utf-8 if ultimately no encoding can be looked up. if ( session.charset === undefined ) { const charsetFound = charsetFromStream(session.buffer); - if ( charsetFound === undefined ) { return streamClose(session); } - const charsetUsed = textEncode.normalizeCharset(charsetFound); - if ( charsetUsed === undefined ) { return streamClose(session); } - session.charset = charsetUsed; + if ( charsetFound !== undefined ) { + const charsetUsed = textEncode.normalizeCharset(charsetFound); + if ( charsetUsed === undefined ) { return streamClose(session); } + session.charset = charsetUsed; + } else { + session.charset = 'utf-8'; + } } while ( session.jobs.length !== 0 ) { From f6e2cb29d545eb6c528bc7a61fd619a34fb607c3 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 5 Mar 2024 11:20:33 -0500 Subject: [PATCH 095/180] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdb042ed13d02..71cb6de458ddd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Fixes / changes +- [Assume UTF-8 when no encoding can be looked up](https://github.com/gorhill/uBlock/commit/63acdcbdeb) +- [Fix issue with "My filters" pane on mobile](https://github.com/gorhill/uBlock/commit/24d94e559d) +- [Support aborting "Pick" mode in element picker](https://github.com/gorhill/uBlock/commit/a557f62112) +- [Remove sections with no lists in "Filter lists" pane](https://github.com/gorhill/uBlock/commit/0f4e50db07) - [Add "Social widgets", "Cookie notices" sections in "Filter lists" pane](https://github.com/gorhill/uBlock/commit/21a76e32a1) - [No longer disable generic cosmetic filters by default on mobile](https://github.com/gorhill/uBlock/commit/7a768e7b1a) - [Improve `spoof-css` scriptlet](https://github.com/gorhill/uBlock/commit/603239970d) From c990e74ee53111019f58b5bd6f8aaa3059d095ff Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 5 Mar 2024 11:50:52 -0500 Subject: [PATCH 096/180] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 349f76fff13e5..4140bee6d89ce 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.56.1.13 +1.56.1.14 From 7dffaa03d558dc2f9c6af7f2f7b32c2314b028e9 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 5 Mar 2024 12:11:22 -0500 Subject: [PATCH 097/180] Make Firefox dev build auto-update --- dist/firefox/updates.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index a0909f2f3f4f5..9c3e504a3706c 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,9 +3,9 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.56.1.13", + "version": "1.56.1.14", "browser_specific_settings": { "gecko": { "strict_min_version": "78.0" } }, - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b13/uBlock0_1.56.1b13.firefox.signed.xpi" + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.56.1b14/uBlock0_1.56.1b14.firefox.signed.xpi" } ] } From 4f6fa840c331d7a23cce1cbaf0aee874d40c3a9d Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 6 Mar 2024 08:45:49 -0500 Subject: [PATCH 098/180] Use `100svh` everywhere `100vh` is used Related issue: https://github.com/uBlockOrigin/uBlock-issues/issues/3058 --- src/css/1p-filters.css | 1 + src/css/advanced-settings.css | 1 + src/css/asset-viewer.css | 1 + src/css/code-viewer.css | 1 + src/css/dashboard.css | 6 ++---- src/css/devtools.css | 1 + src/css/dom-inspector.css | 1 + src/css/dyna-rules.css | 1 + src/css/epicker-ui.css | 1 + src/css/logger-ui.css | 2 ++ src/css/popup-fenix.css | 1 + src/css/whitelist.css | 1 + src/js/scriptlets/epicker.js | 1 + 13 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/css/1p-filters.css b/src/css/1p-filters.css index f6498c493231a..9e087cb7ec1fc 100644 --- a/src/css/1p-filters.css +++ b/src/css/1p-filters.css @@ -1,5 +1,6 @@ html { height: 100vh; + height: 100svh; overflow: hidden; width: 100vw; } diff --git a/src/css/advanced-settings.css b/src/css/advanced-settings.css index c67e750701e5a..10225dd950eee 100644 --- a/src/css/advanced-settings.css +++ b/src/css/advanced-settings.css @@ -1,5 +1,6 @@ html { height: 100vh; + height: 100svh; overflow: hidden; width: 100vw; } diff --git a/src/css/asset-viewer.css b/src/css/asset-viewer.css index 8b6f1da187686..d2df68a1ed818 100644 --- a/src/css/asset-viewer.css +++ b/src/css/asset-viewer.css @@ -23,6 +23,7 @@ body { display: flex; flex-direction: column; height: 100vh; + height: 100svh; margin: 0; overflow: hidden; padding: 0; diff --git a/src/css/code-viewer.css b/src/css/code-viewer.css index 774fa69efa15f..40b5114487db1 100644 --- a/src/css/code-viewer.css +++ b/src/css/code-viewer.css @@ -3,6 +3,7 @@ body { display: flex; flex-direction: column; height: 100vh; + height: 100svh; margin: 0; overflow: hidden; padding: 0; diff --git a/src/css/dashboard.css b/src/css/dashboard.css index ee03ed179f1ae..3a45a65bee3e0 100644 --- a/src/css/dashboard.css +++ b/src/css/dashboard.css @@ -2,6 +2,7 @@ html, body { display: flex; flex-direction: column; height: 100vh; + height: 100svh; justify-content: stretch; overflow: hidden; position: relative; @@ -75,6 +76,7 @@ iframe { } #unsavedWarning > div:last-of-type { height: 100vh; + height: 100svh; position: absolute; width: 100vw; } @@ -105,10 +107,6 @@ body.noDashboard #dashboard-nav { border-bottom-color: var(--dashboard-tab-hover-border); } -/* touch-screen devices */ -:root.mobile, :root.mobile body { - height: 100svh; - } :root.mobile #dashboard-nav { flex-wrap: nowrap; overflow-x: auto; diff --git a/src/css/devtools.css b/src/css/devtools.css index 425aac47589bc..bb4c40dc873d0 100644 --- a/src/css/devtools.css +++ b/src/css/devtools.css @@ -1,5 +1,6 @@ html { height: 100vh; + height: 100svh; overflow: hidden; width: 100vw; } diff --git a/src/css/dom-inspector.css b/src/css/dom-inspector.css index 71ba34826852b..2f2ca14e1eba1 100644 --- a/src/css/dom-inspector.css +++ b/src/css/dom-inspector.css @@ -3,6 +3,7 @@ html#ublock0-inspector, background: transparent; box-sizing: border-box; height: 100vh; + height: 100svh; margin: 0; overflow: hidden; width: 100vw; diff --git a/src/css/dyna-rules.css b/src/css/dyna-rules.css index 35e0f8cbbbdc5..9a6bd8d5c0a3b 100644 --- a/src/css/dyna-rules.css +++ b/src/css/dyna-rules.css @@ -1,5 +1,6 @@ html { height: 100vh; + height: 100svh; overflow: hidden; width: 100vw; } diff --git a/src/css/epicker-ui.css b/src/css/epicker-ui.css index 01d3f05afeedc..8ce5ade80f3db 100644 --- a/src/css/epicker-ui.css +++ b/src/css/epicker-ui.css @@ -3,6 +3,7 @@ html#ublock0-epicker, background: transparent; cursor: not-allowed; height: 100vh; + height: 100svh; margin: 0; overflow: hidden; width: 100vw; diff --git a/src/css/logger-ui.css b/src/css/logger-ui.css index baf4103086276..7ae632e6e7b7f 100644 --- a/src/css/logger-ui.css +++ b/src/css/logger-ui.css @@ -2,6 +2,7 @@ body { display: flex; flex-direction: column; height: 100vh; + height: 100svh; overflow: hidden; width: 100vw; } @@ -512,6 +513,7 @@ body[dir="rtl"] .closeButton { bottom: 0; display: none; max-height: min(800px, calc(100vh - 2rem)); + max-height: min(800px, calc(100svh - 2rem)); min-width: 360px; overflow: hidden; position: fixed; diff --git a/src/css/popup-fenix.css b/src/css/popup-fenix.css index 60cd39def6824..b845562d3a4fc 100644 --- a/src/css/popup-fenix.css +++ b/src/css/popup-fenix.css @@ -300,6 +300,7 @@ body.needSave #revertRules { } :root.desktop body.vMin #firewall { max-height: 100vh; + max-height: 100svh; } #firewall > * { direction: ltr; diff --git a/src/css/whitelist.css b/src/css/whitelist.css index 715c9640dcc99..3248a77f6d603 100644 --- a/src/css/whitelist.css +++ b/src/css/whitelist.css @@ -1,5 +1,6 @@ html { height: 100vh; + height: 100svh; overflow: hidden; width: 100vw; } diff --git a/src/js/scriptlets/epicker.js b/src/js/scriptlets/epicker.js index 9e75b53ccc4cc..41b0b76bd411e 100644 --- a/src/js/scriptlets/epicker.js +++ b/src/js/scriptlets/epicker.js @@ -1255,6 +1255,7 @@ const pickerCSSStyle = [ 'display: block', 'filter: none', 'height: 100vh', + ' height: 100svh', 'left: 0', 'margin: 0', 'max-height: none', From 02966afb8c1e17e0b77cd9fde5050ecfd9f8c578 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 6 Mar 2024 12:46:12 -0500 Subject: [PATCH 099/180] [mv3] Do not pollute browser console in stable release versions --- platform/mv3/extension/js/utils.js | 15 +++++++++++++-- tools/make-mv3.sh | 7 +++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/platform/mv3/extension/js/utils.js b/platform/mv3/extension/js/utils.js index b1a463a77a765..cadeaea0b64c3 100644 --- a/platform/mv3/extension/js/utils.js +++ b/platform/mv3/extension/js/utils.js @@ -131,11 +131,22 @@ export const broadcastMessage = message => { /******************************************************************************/ const ubolLog = (...args) => { - // Do not pollute dev console in stable release. - if ( browser.runtime.id === 'ddkjiahejlhfcafbddmgiahcphecmpfh' ) { return; } + // Do not pollute dev console in stable releases. + if ( shouldLog !== true ) { return; } console.info('[uBOL]', ...args); }; +const shouldLog = (( ) => { + const { id } = browser.runtime; + // https://addons.mozilla.org/en-US/firefox/addon/ublock-origin-lite/ + if ( id === 'uBOLite@raymondhill.net' ) { return false; } + // https://chromewebstore.google.com/detail/ddkjiahejlhfcafbddmgiahcphecmpfh + if ( id === 'ddkjiahejlhfcafbddmgiahcphecmpfh' ) { return false; } + // https://microsoftedge.microsoft.com/addons/detail/cimighlppcgcoapaliogpjjdehbnofhn + if ( id === 'cimighlppcgcoapaliogpjjdehbnofhn' ) { return false; } + return true; +})(); + /******************************************************************************/ export { diff --git a/tools/make-mv3.sh b/tools/make-mv3.sh index 39b0c3879e986..8e155582bd59d 100755 --- a/tools/make-mv3.sh +++ b/tools/make-mv3.sh @@ -124,6 +124,13 @@ fi echo "*** uBOLite.mv3: extension ready" echo "Extension location: $DES/" +# Local build: use a different extension id than the official one +if [ -z "$TAGNAME" ]; then + tmp=$(mktemp) + jq '.browser_specific_settings.gecko.id = "uBOLite.dev@raymondhill.net"' "$DES/manifest.json" > "$tmp" \ + && mv "$tmp" "$DES/manifest.json" +fi + if [ "$FULL" = "yes" ]; then if [ -n "$BEFORE" ]; then echo "*** uBOLite.mv3: salvaging rule ids to minimize diff size" From 8533fa74eef9f7723547a074513e669a91edb03f Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 7 Mar 2024 09:14:30 -0500 Subject: [PATCH 100/180] [mv3] Use EasyList lists for "Annoyances" section --- platform/mv3/make-rulesets.js | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/platform/mv3/make-rulesets.js b/platform/mv3/make-rulesets.js index bf6bfed452dd8..4f79d52dd8b8a 100644 --- a/platform/mv3/make-rulesets.js +++ b/platform/mv3/make-rulesets.js @@ -1225,7 +1225,6 @@ async function main() { for ( const id of handpicked ) { const asset = assets[id]; if ( asset.content !== 'filters' ) { continue; } - const contentURL = Array.isArray(asset.contentURL) ? asset.contentURL[0] : asset.contentURL; @@ -1253,45 +1252,45 @@ async function main() { }); await rulesetFromURLs({ id: 'annoyances-overlays', - name: 'AdGuard/uBO – Overlays', + name: 'EasyList/uBO – Overlay Notices', group: 'annoyances', enabled: false, secret, urls: [ - 'https://filters.adtidy.org/extension/ublock/filters/19.txt', + 'https://ublockorigin.github.io/uAssets/thirdparties/easylist-newsletters.txt', 'https://ublockorigin.github.io/uAssets/filters/annoyances-others.txt', ], - homeURL: 'https://github.com/AdguardTeam/AdguardFilters#adguard-filters', + homeURL: 'https://github.com/easylist/easylist#fanboy-lists', }); await rulesetFromURLs({ id: 'annoyances-social', - name: 'AdGuard – Social Media', + name: 'EasyList – Social Widgets', group: 'annoyances', enabled: false, urls: [ - 'https://filters.adtidy.org/extension/ublock/filters/4.txt', + 'https://ublockorigin.github.io/uAssets/thirdparties/easylist-social.txt', ], - homeURL: 'https://github.com/AdguardTeam/AdguardFilters#adguard-filters', + homeURL: 'https://github.com/easylist/easylist#fanboy-lists', }); await rulesetFromURLs({ id: 'annoyances-widgets', - name: 'AdGuard – Widgets', + name: 'EasyList – Chat Widgets', group: 'annoyances', enabled: false, urls: [ - 'https://filters.adtidy.org/extension/ublock/filters/22.txt', + 'https://ublockorigin.github.io/uAssets/thirdparties/easylist-chat.txt', ], - homeURL: 'https://github.com/AdguardTeam/AdguardFilters#adguard-filters', + homeURL: 'https://github.com/easylist/easylist#fanboy-lists', }); await rulesetFromURLs({ id: 'annoyances-others', - name: 'AdGuard – Other Annoyances', + name: 'EasyList – Other Annoyances', group: 'annoyances', enabled: false, urls: [ - 'https://filters.adtidy.org/extension/ublock/filters/21.txt', + 'https://ublockorigin.github.io/uAssets/thirdparties/easylist-annoyances.txt' ], - homeURL: 'https://github.com/AdguardTeam/AdguardFilters#adguard-filters', + homeURL: 'https://github.com/easylist/easylist#fanboy-lists', }); // Handpicked rulesets from abroad From e1e861704bd77840bf5e02563fc701e7e15f8dda Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 7 Mar 2024 09:19:16 -0500 Subject: [PATCH 101/180] [mv3] Fix build script --- tools/make-mv3.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/make-mv3.sh b/tools/make-mv3.sh index 8e155582bd59d..7d9af6cabfb58 100755 --- a/tools/make-mv3.sh +++ b/tools/make-mv3.sh @@ -125,7 +125,7 @@ echo "*** uBOLite.mv3: extension ready" echo "Extension location: $DES/" # Local build: use a different extension id than the official one -if [ -z "$TAGNAME" ]; then +if [ -z "$TAGNAME" ] && [ "$PLATFORM" = "firefox" ]; then tmp=$(mktemp) jq '.browser_specific_settings.gecko.id = "uBOLite.dev@raymondhill.net"' "$DES/manifest.json" > "$tmp" \ && mv "$tmp" "$DES/manifest.json" From d5c359d411899f5d76d861faf616921fb943644b Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 7 Mar 2024 13:09:24 -0500 Subject: [PATCH 102/180] Import translation work from https://crowdin.com/project/ublock --- platform/mv3/extension/_locales/sq/messages.json | 2 +- src/_locales/ca/messages.json | 4 ++-- src/_locales/el/messages.json | 4 ++-- src/_locales/ko/messages.json | 4 ++-- src/_locales/pt_PT/messages.json | 4 ++-- src/_locales/sq/messages.json | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/platform/mv3/extension/_locales/sq/messages.json b/platform/mv3/extension/_locales/sq/messages.json index d390d8dae520f..c666c342ffd52 100644 --- a/platform/mv3/extension/_locales/sq/messages.json +++ b/platform/mv3/extension/_locales/sq/messages.json @@ -144,7 +144,7 @@ "description": "This describes the 'complete' filtering mode" }, "noFilteringModeDescription": { - "message": "Emrat e hosteve që nuk do të kalojnë në filtër", + "message": "Emrat e hosteve, që nuk do të kalojnë në filtër.", "description": "A short description for the editable field which lists trusted sites" }, "behaviorSectionLabel": { diff --git a/src/_locales/ca/messages.json b/src/_locales/ca/messages.json index ff395ddcdb7e7..992d545aed6f7 100644 --- a/src/_locales/ca/messages.json +++ b/src/_locales/ca/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Ginys socials", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Avís de galetes", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/el/messages.json b/src/_locales/el/messages.json index 7e9baf6edddb7..f8a6455f015cb 100644 --- a/src/_locales/el/messages.json +++ b/src/_locales/el/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Γραφικά στοιχεία κοινωνικής δικτύωσης", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Ειδοποιήσεις για cookies", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/ko/messages.json b/src/_locales/ko/messages.json index a8bc25e190cbe..16437529b34c2 100644 --- a/src/_locales/ko/messages.json +++ b/src/_locales/ko/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "소셜 위젯", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "쿠키 알림", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/pt_PT/messages.json b/src/_locales/pt_PT/messages.json index 20058902d8d6b..223df9df36e31 100644 --- a/src/_locales/pt_PT/messages.json +++ b/src/_locales/pt_PT/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Widgets sociais", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Avisos de cookie", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/sq/messages.json b/src/_locales/sq/messages.json index a0fb18f9df3eb..6b553929ec708 100644 --- a/src/_locales/sq/messages.json +++ b/src/_locales/sq/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Veglat sociale", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Njoftimi për cookies", "description": "Filter lists section name" }, "3pGroupAnnoyances": { From 640eaf89d0e3fe7de9fa8a84524128e4133ce93b Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 11 Mar 2024 08:38:57 -0400 Subject: [PATCH 103/180] Import translation work from https://crowdin.com/project/ublock --- src/_locales/es/messages.json | 4 ++-- src/_locales/hu/messages.json | 2 +- src/_locales/nb/messages.json | 4 ++-- src/_locales/tr/messages.json | 2 +- src/_locales/zh_CN/messages.json | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/_locales/es/messages.json b/src/_locales/es/messages.json index 7e804129454a8..f3a256fcf7ab0 100644 --- a/src/_locales/es/messages.json +++ b/src/_locales/es/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Artilugios sociales", + "message": "Dispositivos sociales", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Avisos de cookies", + "message": "Avisos de cookie's", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/hu/messages.json b/src/_locales/hu/messages.json index 296db18a45767..1bbaefaafb833 100644 --- a/src/_locales/hu/messages.json +++ b/src/_locales/hu/messages.json @@ -488,7 +488,7 @@ "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie értesítések", + "message": "Sütiértesítések", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/nb/messages.json b/src/_locales/nb/messages.json index 0b4196b1a50b6..cf55c1d7fa69d 100644 --- a/src/_locales/nb/messages.json +++ b/src/_locales/nb/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "Sosiale medie-widgeter", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Cookie meldinger", "description": "Filter lists section name" }, "3pGroupAnnoyances": { diff --git a/src/_locales/tr/messages.json b/src/_locales/tr/messages.json index 451dc6ab71ee7..f3eeb96132a13 100644 --- a/src/_locales/tr/messages.json +++ b/src/_locales/tr/messages.json @@ -592,7 +592,7 @@ "description": "" }, "rulesExport": { - "message": "Dosyaya aktar", + "message": "Dosyaya aktar…", "description": "Button in the 'My rules' pane" }, "rulesDefaultFileName": { diff --git a/src/_locales/zh_CN/messages.json b/src/_locales/zh_CN/messages.json index 924588e216357..6c2ac9c597e3f 100644 --- a/src/_locales/zh_CN/messages.json +++ b/src/_locales/zh_CN/messages.json @@ -484,11 +484,11 @@ "description": "Filter lists section name" }, "3pGroupSocial": { - "message": "Social widgets", + "message": "社交网络小部件", "description": "Filter lists section name" }, "3pGroupCookies": { - "message": "Cookie notices", + "message": "Cookie提醒", "description": "Filter lists section name" }, "3pGroupAnnoyances": { From 46ea5519c1a3613d26fc1a02687570e4b84ad3df Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 11 Mar 2024 11:39:31 -0400 Subject: [PATCH 104/180] Add checkboxes to "My filters" pane Related issues: - https://github.com/uBlockOrigin/uBlock-issues/issues/3161 - https://github.com/uBlockOrigin/uBlock-issues/discussions/2895#discussioncomment-8504374 Two checkboxes have been added to the "My filters "pane: 1. A checkbox to wholly disable/enable "My filters". This is equivalent to the checkbox for "My filters" in "Filter lists" pane. 2. A checkbox to enable/disable the trustworthiness of the content of "My filters". Default to untrusted. Since toggling these checkboxes requires reloading all filter lists, their new state must be committed through the "Apply changes" button. Additionally: a "book" icon has been added to the top-right of the dashboard, which is a link to the wiki according to whichever pane is currently active. --- src/1p-filters.html | 7 +- src/3p-filters.html | 4 +- src/_locales/ar/messages.json | 12 +- src/_locales/az/messages.json | 12 +- src/_locales/be/messages.json | 12 +- src/_locales/bg/messages.json | 12 +- src/_locales/bn/messages.json | 12 +- src/_locales/br_FR/messages.json | 12 +- src/_locales/bs/messages.json | 12 +- src/_locales/ca/messages.json | 12 +- src/_locales/cs/messages.json | 12 +- src/_locales/cv/messages.json | 12 +- src/_locales/cy/messages.json | 12 +- src/_locales/da/messages.json | 12 +- src/_locales/de/messages.json | 12 +- src/_locales/el/messages.json | 12 +- src/_locales/en/messages.json | 12 +- src/_locales/en_GB/messages.json | 12 +- src/_locales/eo/messages.json | 12 +- src/_locales/es/messages.json | 12 +- src/_locales/et/messages.json | 12 +- src/_locales/eu/messages.json | 12 +- src/_locales/fa/messages.json | 12 +- src/_locales/fi/messages.json | 12 +- src/_locales/fil/messages.json | 12 +- src/_locales/fr/messages.json | 12 +- src/_locales/fy/messages.json | 12 +- src/_locales/gl/messages.json | 12 +- src/_locales/gu/messages.json | 12 +- src/_locales/he/messages.json | 12 +- src/_locales/hi/messages.json | 12 +- src/_locales/hr/messages.json | 12 +- src/_locales/hu/messages.json | 12 +- src/_locales/hy/messages.json | 12 +- src/_locales/id/messages.json | 12 +- src/_locales/it/messages.json | 12 +- src/_locales/ja/messages.json | 12 +- src/_locales/ka/messages.json | 12 +- src/_locales/kk/messages.json | 12 +- src/_locales/kn/messages.json | 12 +- src/_locales/ko/messages.json | 12 +- src/_locales/lt/messages.json | 12 +- src/_locales/lv/messages.json | 12 +- src/_locales/mk/messages.json | 12 +- src/_locales/ml/messages.json | 12 +- src/_locales/mr/messages.json | 12 +- src/_locales/ms/messages.json | 12 +- src/_locales/nb/messages.json | 12 +- src/_locales/nl/messages.json | 12 +- src/_locales/oc/messages.json | 12 +- src/_locales/pa/messages.json | 12 +- src/_locales/pl/messages.json | 12 +- src/_locales/pt_BR/messages.json | 12 +- src/_locales/pt_PT/messages.json | 12 +- src/_locales/ro/messages.json | 12 +- src/_locales/ru/messages.json | 12 +- src/_locales/si/messages.json | 12 +- src/_locales/sk/messages.json | 12 +- src/_locales/sl/messages.json | 12 +- src/_locales/so/messages.json | 12 +- src/_locales/sq/messages.json | 12 +- src/_locales/sr/messages.json | 12 +- src/_locales/sv/messages.json | 12 +- src/_locales/sw/messages.json | 12 +- src/_locales/ta/messages.json | 12 +- src/_locales/te/messages.json | 12 +- src/_locales/th/messages.json | 12 +- src/_locales/tr/messages.json | 12 +- src/_locales/uk/messages.json | 12 +- src/_locales/ur/messages.json | 12 +- src/_locales/vi/messages.json | 12 +- src/_locales/zh_CN/messages.json | 12 +- src/_locales/zh_TW/messages.json | 12 +- src/css/3p-filters.css | 1 - src/css/common.css | 16 +++ src/css/dashboard.css | 9 +- src/css/fa-icons.css | 1 + src/dashboard.html | 33 +++-- src/dyna-rules.html | 2 +- src/img/fontawesome/fontawesome-defs.svg | 1 + src/js/1p-filters.js | 82 ++++++++--- src/js/3p-filters.js | 2 + src/js/background.js | 2 +- src/js/codemirror/ubo-static-filtering.js | 158 +++++++++++++--------- src/js/dashboard.js | 18 ++- src/js/dyna-rules.js | 33 ++--- src/js/fa-icons.js | 1 + src/js/messaging.js | 14 +- src/js/settings.js | 53 +++++--- src/js/storage.js | 3 + src/js/whitelist.js | 52 +++---- src/whitelist.html | 7 +- 92 files changed, 886 insertions(+), 465 deletions(-) diff --git a/src/1p-filters.html b/src/1p-filters.html index 965972598b6f0..bc08479c5c2e4 100644 --- a/src/1p-filters.html +++ b/src/1p-filters.html @@ -22,15 +22,16 @@
- -

question-circle

-   +  

+

+
+