From f55c891d5457bd0f5830b46148b1cf2ede9d36a0 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 20 Jul 2020 17:28:33 -0400 Subject: [PATCH 01/38] Avoid returning a value when injected as content script --- src/lib/diff/swatinem_diff.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/lib/diff/swatinem_diff.js b/src/lib/diff/swatinem_diff.js index 0601df0eeaf2f..481f2cfbc45e9 100644 --- a/src/lib/diff/swatinem_diff.js +++ b/src/lib/diff/swatinem_diff.js @@ -241,3 +241,23 @@ return Diff; })(self); + + + + + + + + +/******************************************************************************* + + DO NOT: + - Remove the following code + - Add code beyond the following code + Reason: + - https://github.com/gorhill/uBlock/pull/3721 + - uBO never uses the return value from injected content scripts + +**/ + +void 0; From 48325529506122445e5d06b1ba05eff32e348fcc Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 21 Jul 2020 07:15:27 -0400 Subject: [PATCH 02/38] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 7747a5cbb3697..074530a91c8a4 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.28.5.4 +1.28.5.5 From 3839d05a994c8e30033119a71f8aec084cfc2c88 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 21 Jul 2020 07:50:36 -0400 Subject: [PATCH 03/38] Auto-update most obsolete asset first Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/1165 --- src/js/assets.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/js/assets.js b/src/js/assets.js index aac3126e15f33..a6be36169e8cc 100644 --- a/src/js/assets.js +++ b/src/js/assets.js @@ -884,7 +884,7 @@ const updateNext = async function() { ]); const now = Date.now(); - let assetKeyToUpdate; + const toUpdate = []; for ( const assetKey in assetDict ) { const assetEntry = assetDict[assetKey]; if ( assetEntry.hasRemoteURL !== true ) { continue; } @@ -902,23 +902,30 @@ const updateNext = async function() { type: assetEntry.content }) === true ) { - assetKeyToUpdate = assetKey; - break; + toUpdate.push(assetKey); + continue; } // This will remove a cached asset when it's no longer in use. if ( cacheEntry && cacheEntry.readTime < assetCacheRegistryStartTime ) { assetCacheRemove(assetKey); } } - if ( assetKeyToUpdate === undefined ) { + if ( toUpdate.length === 0 ) { return updateDone(); } - updaterFetched.add(assetKeyToUpdate); + // https://github.com/uBlockOrigin/uBlock-issues/issues/1165 + // Update most obsolete asset first. + toUpdate.sort((a, b) => { + const ta = cacheDict[a] !== undefined ? cacheDict[a].writeTime : 0; + const tb = cacheDict[b] !== undefined ? cacheDict[b].writeTime : 0; + return ta - tb; + }); + updaterFetched.add(toUpdate[0]); // In auto-update context, be gentle on remote servers. remoteServerFriendly = updaterAuto; - const result = await getRemote(assetKeyToUpdate); + const result = await getRemote(toUpdate[0]); remoteServerFriendly = false; From 603bd2da8bf9a3abd8405f8a004b07107972f8ea Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Tue, 21 Jul 2020 08:10:29 -0400 Subject: [PATCH 04/38] Make Firefox dev build auto-update --- dist/firefox/updates.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index c9c95dbda8f7c..a08cf295e84b3 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,10 +3,10 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.28.5.4", + "version": "1.28.5.5", "browser_specific_settings": { "gecko": { "strict_min_version": "55" } }, - "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b4", - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b4/uBlock0_1.28.5b4.firefox.signed.xpi" + "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b5", + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b5/uBlock0_1.28.5b5.firefox.signed.xpi" } ] } From 5c68867b92735931a791dfedf4ef9608cc364862 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 22 Jul 2020 10:21:16 -0400 Subject: [PATCH 05/38] Deprecate pseudo user styles code The pseudo user styles code served only browsers based on Chromium 65 and earlier -- Chromium 66 supports native user styles and was first released more than two years ago. In Chromium-based browsers, the pseudo user styles code is being unconditionally injected in every page/frame just in case the browser is version 65 or earlier. Removing pseudo user styles reduce uBO's main content script in Chromium-based browsers by more than 20K. Related thread: - https://github.com/NanoAdblocker/NanoCore/issues/348#issuecomment-653646507 --- platform/chromium/vapi-usercss.js | 54 --- platform/chromium/vapi-usercss.pseudo.js | 562 ----------------------- platform/chromium/vapi-usercss.real.js | 282 ------------ platform/firefox/vapi-usercss.js | 48 -- platform/webext/vapi-usercss.js | 54 --- src/js/contentscript.js | 315 ++++++++++--- tools/make-chromium.sh | 13 - tools/make-firefox.sh | 12 - tools/make-opera.sh | 13 - tools/make-webext.sh | 15 - 10 files changed, 261 insertions(+), 1107 deletions(-) delete mode 100644 platform/chromium/vapi-usercss.js delete mode 100644 platform/chromium/vapi-usercss.pseudo.js delete mode 100644 platform/chromium/vapi-usercss.real.js delete mode 100644 platform/firefox/vapi-usercss.js delete mode 100644 platform/webext/vapi-usercss.js diff --git a/platform/chromium/vapi-usercss.js b/platform/chromium/vapi-usercss.js deleted file mode 100644 index 0cf97b7aa4162..0000000000000 --- a/platform/chromium/vapi-usercss.js +++ /dev/null @@ -1,54 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2018 Raymond Hill - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see {http://www.gnu.org/licenses/}. - - Home: https://github.com/gorhill/uBlock -*/ - -'use strict'; - -// This file can be replaced by platform-specific code. If a platform is -// known to NOT support user stylsheets, vAPI.supportsUserStylesheets can be -// set to `false`. - -// Chromium 66 and above supports user stylesheets: -// https://github.com/gorhill/uBlock/issues/3588 - -if ( typeof vAPI === 'object' ) { - vAPI.supportsUserStylesheets = - /\bChrom(?:e|ium)\/(?:5\d|6[012345])\b/.test(navigator.userAgent) === false; -} - - - - - - - - -/******************************************************************************* - - DO NOT: - - Remove the following code - - Add code beyond the following code - Reason: - - https://github.com/gorhill/uBlock/pull/3721 - - uBO never uses the return value from injected content scripts - -**/ - -void 0; diff --git a/platform/chromium/vapi-usercss.pseudo.js b/platform/chromium/vapi-usercss.pseudo.js deleted file mode 100644 index 4d0fda69f35cd..0000000000000 --- a/platform/chromium/vapi-usercss.pseudo.js +++ /dev/null @@ -1,562 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2017-2018 Raymond Hill - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see {http://www.gnu.org/licenses/}. - - Home: https://github.com/gorhill/uBlock -*/ - -'use strict'; - -// Packaging this file is optional: it is not necessary to package it if the -// platform is known to support user stylesheets. - -// >>>>>>>> start of HUGE-IF-BLOCK -if ( typeof vAPI === 'object' && vAPI.userStylesheet === undefined ) { - -/******************************************************************************/ -/******************************************************************************/ - -vAPI.userStylesheet = { - style: null, - styleFixCount: 0, - css: new Map(), - disabled: false, - apply: function() { - }, - inject: function() { - this.style = document.createElement('style'); - this.style.disabled = this.disabled; - const parent = document.head || document.documentElement; - if ( parent === null ) { return; } - parent.appendChild(this.style); - const observer = new MutationObserver(function() { - if ( this.style === null ) { return; } - if ( this.style.sheet !== null ) { return; } - this.styleFixCount += 1; - if ( this.styleFixCount < 32 ) { - parent.appendChild(this.style); - } else { - observer.disconnect(); - } - }.bind(this)); - observer.observe(parent, { childList: true }); - }, - add: function(cssText) { - if ( cssText === '' || this.css.has(cssText) ) { return; } - if ( this.style === null ) { this.inject(); } - const sheet = this.style.sheet; - if ( !sheet ) { return; } - const i = sheet.cssRules.length; - sheet.insertRule(cssText, i); - this.css.set(cssText, sheet.cssRules[i]); - }, - remove: function(cssText) { - if ( cssText === '' ) { return; } - const cssRule = this.css.get(cssText); - if ( cssRule === undefined ) { return; } - this.css.delete(cssText); - if ( this.style === null ) { return; } - const sheet = this.style.sheet; - if ( !sheet ) { return; } - const rules = sheet.cssRules; - let i = rules.length; - while ( i-- ) { - if ( rules[i] !== cssRule ) { continue; } - sheet.deleteRule(i); - break; - } - if ( rules.length !== 0 ) { return; } - const style = this.style; - this.style = null; - const parent = style.parentNode; - if ( parent !== null ) { - parent.removeChild(style); - } - }, - toggle: function(state) { - if ( state === undefined ) { state = this.disabled; } - if ( state !== this.disabled ) { return; } - this.disabled = !state; - if ( this.style !== null ) { - this.style.disabled = this.disabled; - } - } -}; - -/******************************************************************************/ - -vAPI.DOMFilterer = class { - constructor() { - this.commitTimer = new vAPI.SafeAnimationFrame(this.commitNow.bind(this)); - this.domIsReady = document.readyState !== 'loading'; - this.listeners = []; - this.excludedNodeSet = new WeakSet(); - this.addedNodes = new Set(); - this.removedNodes = false; - - this.specificSimpleHide = new Set(); - this.specificSimpleHideAggregated = undefined; - this.addedSpecificSimpleHide = []; - this.specificComplexHide = new Set(); - this.specificComplexHideAggregated = undefined; - this.addedSpecificComplexHide = []; - this.specificOthers = []; - this.genericSimpleHide = new Set(); - this.genericComplexHide = new Set(); - this.exceptedCSSRules = []; - - this.hideNodeExpando = undefined; - this.hideNodeBatchProcessTimer = undefined; - this.hiddenNodeObserver = undefined; - this.hiddenNodesetToProcess = new Set(); - this.hiddenNodeset = new WeakSet(); - - if ( vAPI.domWatcher instanceof Object ) { - vAPI.domWatcher.addListener(this); - } - - // https://www.w3.org/community/webed/wiki/CSS/Selectors#Combinators - this.reCSSCombinators = /[ >+~]/; - } - - commitNow() { - this.commitTimer.clear(); - - if ( this.domIsReady !== true || vAPI.userStylesheet.disabled ) { - return; - } - - // Filterset changed. - - if ( this.addedSpecificSimpleHide.length !== 0 ) { - //console.time('specific simple filterset changed'); - //console.log('added %d specific simple selectors', this.addedSpecificSimpleHide.length); - const nodes = document.querySelectorAll(this.addedSpecificSimpleHide.join(',')); - for ( const node of nodes ) { - this.hideNode(node); - } - this.addedSpecificSimpleHide = []; - this.specificSimpleHideAggregated = undefined; - //console.timeEnd('specific simple filterset changed'); - } - - if ( this.addedSpecificComplexHide.length !== 0 ) { - //console.time('specific complex filterset changed'); - //console.log('added %d specific complex selectors', this.addedSpecificComplexHide.length); - const nodes = document.querySelectorAll(this.addedSpecificComplexHide.join(',')); - for ( const node of nodes ) { - this.hideNode(node); - } - this.addedSpecificComplexHide = []; - this.specificComplexHideAggregated = undefined; - //console.timeEnd('specific complex filterset changed'); - } - - // DOM layout changed. - - const domNodesAdded = this.addedNodes.size !== 0; - const domLayoutChanged = domNodesAdded || this.removedNodes; - - if ( domNodesAdded === false || domLayoutChanged === false ) { - return; - } - - //console.log('%d nodes added', this.addedNodes.size); - - if ( this.specificSimpleHide.size !== 0 && domNodesAdded ) { - //console.time('dom layout changed/specific simple selectors'); - if ( this.specificSimpleHideAggregated === undefined ) { - this.specificSimpleHideAggregated = - Array.from(this.specificSimpleHide).join(',\n'); - } - for ( const node of this.addedNodes ) { - if ( node.matches(this.specificSimpleHideAggregated) ) { - this.hideNode(node); - } - const nodes = node.querySelectorAll(this.specificSimpleHideAggregated); - for ( const node of nodes ) { - this.hideNode(node); - } - } - //console.timeEnd('dom layout changed/specific simple selectors'); - } - - if ( this.specificComplexHide.size !== 0 && domLayoutChanged ) { - //console.time('dom layout changed/specific complex selectors'); - if ( this.specificComplexHideAggregated === undefined ) { - this.specificComplexHideAggregated = - Array.from(this.specificComplexHide).join(',\n'); - } - const nodes = document.querySelectorAll(this.specificComplexHideAggregated); - for ( const node of nodes ) { - this.hideNode(node); - } - //console.timeEnd('dom layout changed/specific complex selectors'); - } - - this.addedNodes.clear(); - this.removedNodes = false; - } - - commit(now) { - if ( now ) { - this.commitTimer.clear(); - this.commitNow(); - } else { - this.commitTimer.start(); - } - } - - addCSSRule(selectors, declarations, details = {}) { - if ( selectors === undefined ) { return; } - - const selectorsStr = Array.isArray(selectors) ? - selectors.join(',\n') : - selectors; - if ( selectorsStr.length === 0 ) { return; } - - vAPI.userStylesheet.add(selectorsStr + '\n{' + declarations + '}'); - this.commit(); - if ( details.silent !== true && this.hasListeners() ) { - this.triggerListeners({ - declarative: [ [ selectorsStr, declarations ] ] - }); - } - - if ( declarations !== 'display:none!important;' ) { - this.specificOthers.push({ - selectors: selectorsStr, - declarations: declarations - }); - return; - } - - const isGeneric= details.lazy === true; - const isSimple = details.type === 'simple'; - const isComplex = details.type === 'complex'; - - if ( isGeneric ) { - if ( isSimple ) { - this.genericSimpleHide.add(selectorsStr); - return; - } - if ( isComplex ) { - this.genericComplexHide.add(selectorsStr); - return; - } - } - - const selectorsArr = Array.isArray(selectors) ? - selectors : - selectors.split(',\n'); - - if ( isGeneric ) { - for ( const selector of selectorsArr ) { - if ( this.reCSSCombinators.test(selector) ) { - this.genericComplexHide.add(selector); - } else { - this.genericSimpleHide.add(selector); - } - } - return; - } - - // Specific cosmetic filters. - for ( const selector of selectorsArr ) { - if ( - isComplex || - isSimple === false && this.reCSSCombinators.test(selector) - ) { - if ( this.specificComplexHide.has(selector) === false ) { - this.specificComplexHide.add(selector); - this.addedSpecificComplexHide.push(selector); - } - } else if ( this.specificSimpleHide.has(selector) === false ) { - this.specificSimpleHide.add(selector); - this.addedSpecificSimpleHide.push(selector); - } - } - } - - exceptCSSRules(exceptions) { - if ( exceptions.length === 0 ) { return; } - this.exceptedCSSRules.push(...exceptions); - if ( this.hasListeners() ) { - this.triggerListeners({ exceptions }); - } - } - - onDOMCreated() { - this.domIsReady = true; - this.addedNodes.clear(); - this.removedNodes = false; - this.commit(); - } - - onDOMChanged(addedNodes, removedNodes) { - for ( const node of addedNodes ) { - this.addedNodes.add(node); - } - this.removedNodes = this.removedNodes || removedNodes; - this.commit(); - } - - addListener(listener) { - if ( this.listeners.indexOf(listener) !== -1 ) { return; } - this.listeners.push(listener); - } - - removeListener(listener) { - const pos = this.listeners.indexOf(listener); - if ( pos === -1 ) { return; } - this.listeners.splice(pos, 1); - } - - hasListeners() { - return this.listeners.length !== 0; - } - - triggerListeners(changes) { - for ( const listener of this.listeners ) { - listener.onFiltersetChanged(changes); - } - } - - // https://jsperf.com/clientheight-and-clientwidth-vs-getcomputedstyle - // Avoid getComputedStyle(), detecting whether a node is visible can be - // achieved with clientWidth/clientHeight. - // https://gist.github.com/paulirish/5d52fb081b3570c81e3a - // Do not interleave read-from/write-to the DOM. Write-to DOM - // operations would cause the first read-from to be expensive, and - // interleaving means that potentially all single read-from operation - // would be expensive rather than just the 1st one. - // Benchmarking toggling off/on cosmetic filtering confirms quite an - // improvement when: - // - batching as much as possible handling of all nodes; - // - avoiding to interleave read-from/write-to operations. - // However, toggling off/on cosmetic filtering repeatedly is not - // a real use case, but this shows this will help performance - // on sites which try to use inline styles to bypass blockers. - hideNodeBatchProcess() { - this.hideNodeBatchProcessTimer.clear(); - const expando = this.hideNodeExpando; - for ( const node of this.hiddenNodesetToProcess ) { - if ( - this.hiddenNodeset.has(node) === false || - node[expando] === undefined || - node.clientHeight === 0 || node.clientWidth === 0 - ) { - continue; - } - let attr = node.getAttribute('style'); - if ( attr === null ) { - attr = ''; - } else if ( attr.length !== 0 ) { - if ( attr.endsWith('display:none!important;') ) { continue; } - if ( attr.charCodeAt(attr.length - 1) !== 0x3B /* ';' */ ) { - attr += ';'; - } - } - node.setAttribute('style', attr + 'display:none!important;'); - } - this.hiddenNodesetToProcess.clear(); - } - - hideNodeObserverHandler(mutations) { - if ( vAPI.userStylesheet.disabled ) { return; } - const stagedNodes = this.hiddenNodesetToProcess; - for ( const mutation of mutations ) { - stagedNodes.add(mutation.target); - } - this.hideNodeBatchProcessTimer.start(); - } - - hideNodeInit() { - this.hideNodeExpando = vAPI.randomToken(); - this.hideNodeBatchProcessTimer = - new vAPI.SafeAnimationFrame(this.hideNodeBatchProcess.bind(this)); - this.hiddenNodeObserver = - new MutationObserver(this.hideNodeObserverHandler.bind(this)); - if ( this.hideNodeStyleSheetInjected === false ) { - this.hideNodeStyleSheetInjected = true; - vAPI.userStylesheet.add( - `[${this.hideNodeAttr}]\n{display:none!important;}` - ); - } - } - - excludeNode(node) { - this.excludedNodeSet.add(node); - this.unhideNode(node); - } - - unexcludeNode(node) { - this.excludedNodeSet.delete(node); - } - - hideNode(node) { - if ( this.excludedNodeSet.has(node) ) { return; } - if ( this.hideNodeAttr === undefined ) { return; } - if ( this.hiddenNodeset.has(node) ) { return; } - node.hidden = true; - this.hiddenNodeset.add(node); - if ( this.hideNodeExpando === undefined ) { this.hideNodeInit(); } - node.setAttribute(this.hideNodeAttr, ''); - if ( node[this.hideNodeExpando] === undefined ) { - node[this.hideNodeExpando] = - node.hasAttribute('style') && - (node.getAttribute('style') || ''); - } - this.hiddenNodesetToProcess.add(node); - this.hideNodeBatchProcessTimer.start(); - this.hiddenNodeObserver.observe(node, this.hiddenNodeObserverOptions); - } - - unhideNode(node) { - if ( this.hiddenNodeset.has(node) === false ) { return; } - node.hidden = false; - node.removeAttribute(this.hideNodeAttr); - this.hiddenNodesetToProcess.delete(node); - if ( this.hideNodeExpando === undefined ) { return; } - const attr = node[this.hideNodeExpando]; - if ( attr === false ) { - node.removeAttribute('style'); - } else if ( typeof attr === 'string' ) { - node.setAttribute('style', attr); - } - node[this.hideNodeExpando] = undefined; - this.hiddenNodeset.delete(node); - } - - showNode(node) { - node.hidden = false; - const attr = node[this.hideNodeExpando]; - if ( attr === false ) { - node.removeAttribute('style'); - } else if ( typeof attr === 'string' ) { - node.setAttribute('style', attr); - } - } - - unshowNode(node) { - node.hidden = true; - this.hiddenNodesetToProcess.add(node); - } - - toggle(state, callback) { - vAPI.userStylesheet.toggle(state); - const disabled = vAPI.userStylesheet.disabled; - const nodes = document.querySelectorAll(`[${this.hideNodeAttr}]`); - for ( const node of nodes ) { - if ( disabled ) { - this.showNode(node); - } else { - this.unshowNode(node); - } - } - if ( disabled === false && this.hideNodeExpando !== undefined ) { - this.hideNodeBatchProcessTimer.start(); - } - if ( typeof callback === 'function' ) { - callback(); - } - } - - getAllSelectors_(all) { - const out = { - declarative: [], - exceptions: this.exceptedCSSRules, - }; - if ( this.specificSimpleHide.size !== 0 ) { - out.declarative.push([ - Array.from(this.specificSimpleHide).join(',\n'), - 'display:none!important;' - ]); - } - if ( this.specificComplexHide.size !== 0 ) { - out.declarative.push([ - Array.from(this.specificComplexHide).join(',\n'), - 'display:none!important;' - ]); - } - if ( this.genericSimpleHide.size !== 0 ) { - out.declarative.push([ - Array.from(this.genericSimpleHide).join(',\n'), - 'display:none!important;' - ]); - } - if ( this.genericComplexHide.size !== 0 ) { - out.declarative.push([ - Array.from(this.genericComplexHide).join(',\n'), - 'display:none!important;' - ]); - } - if ( all ) { - out.declarative.push([ - '[' + this.hideNodeAttr + ']', - 'display:none!important;' - ]); - } - for ( const entry of this.specificOthers ) { - out.declarative.push([ entry.selectors, entry.declarations ]); - } - return out; - } - - getFilteredElementCount() { - const details = this.getAllSelectors_(true); - if ( Array.isArray(details.declarative) === false ) { return 0; } - const selectors = details.declarative.map(entry => entry[0]); - if ( selectors.length === 0 ) { return 0; } - return document.querySelectorAll(selectors.join(',\n')).length; - } - - getAllSelectors() { - return this.getAllSelectors_(false); - } -}; - -vAPI.DOMFilterer.prototype.hiddenNodeObserverOptions = { - attributes: true, - attributeFilter: [ 'style' ] -}; - -/******************************************************************************/ -/******************************************************************************/ - -} -// <<<<<<<< end of HUGE-IF-BLOCK - - - - - - - - -/******************************************************************************* - - DO NOT: - - Remove the following code - - Add code beyond the following code - Reason: - - https://github.com/gorhill/uBlock/pull/3721 - - uBO never uses the return value from injected content scripts - -**/ - -void 0; diff --git a/platform/chromium/vapi-usercss.real.js b/platform/chromium/vapi-usercss.real.js deleted file mode 100644 index 8ec746f6de57a..0000000000000 --- a/platform/chromium/vapi-usercss.real.js +++ /dev/null @@ -1,282 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2017-present Raymond Hill - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see {http://www.gnu.org/licenses/}. - - Home: https://github.com/gorhill/uBlock -*/ - -'use strict'; - -// Packaging this file is optional: it is not necessary to package it if the -// platform is known to not support user stylesheets. - -// >>>>>>>> start of HUGE-IF-BLOCK -if ( typeof vAPI === 'object' && vAPI.supportsUserStylesheets ) { - -/******************************************************************************/ -/******************************************************************************/ - -vAPI.userStylesheet = { - added: new Set(), - removed: new Set(), - apply: function(callback) { - if ( this.added.size === 0 && this.removed.size === 0 ) { return; } - vAPI.messaging.send('vapi', { - what: 'userCSS', - add: Array.from(this.added), - remove: Array.from(this.removed), - }).then(( ) => { - if ( callback instanceof Function === false ) { return; } - callback(); - }); - this.added.clear(); - this.removed.clear(); - }, - add: function(cssText, now) { - if ( cssText === '' ) { return; } - this.added.add(cssText); - if ( now ) { this.apply(); } - }, - remove: function(cssText, now) { - if ( cssText === '' ) { return; } - this.removed.add(cssText); - if ( now ) { this.apply(); } - } -}; - -/******************************************************************************/ - -vAPI.DOMFilterer = class { - constructor() { - this.commitTimer = new vAPI.SafeAnimationFrame(this.commitNow.bind(this)); - this.domIsReady = document.readyState !== 'loading'; - this.disabled = false; - this.listeners = []; - this.filterset = new Set(); - this.excludedNodeSet = new WeakSet(); - this.addedCSSRules = new Set(); - this.exceptedCSSRules = []; - this.reOnlySelectors = /\n\{[^\n]+/g; - - // https://github.com/uBlockOrigin/uBlock-issues/issues/167 - // By the time the DOMContentLoaded is fired, the content script might - // have been disconnected from the background page. Unclear why this - // would happen, so far seems to be a Chromium-specific behavior at - // launch time. - if ( this.domIsReady !== true ) { - document.addEventListener('DOMContentLoaded', ( ) => { - if ( vAPI instanceof Object === false ) { return; } - this.domIsReady = true; - this.commit(); - }); - } - } - - // Here we will deal with: - // - Injecting low priority user styles; - // - Notifying listeners about changed filterset. - // https://www.reddit.com/r/uBlockOrigin/comments/9jj0y1/no_longer_blocking_ads/ - // Ensure vAPI is still valid -- it can go away by the time we are - // called, since the port could be force-disconnected from the main - // process. Another approach would be to have vAPI.SafeAnimationFrame - // register a shutdown job: to evaluate. For now I will keep the fix - // trivial. - commitNow() { - this.commitTimer.clear(); - if ( vAPI instanceof Object === false ) { return; } - const userStylesheet = vAPI.userStylesheet; - for ( const entry of this.addedCSSRules ) { - if ( - this.disabled === false && - entry.lazy && - entry.injected === false - ) { - userStylesheet.add( - entry.selectors + '\n{' + entry.declarations + '}' - ); - } - } - this.addedCSSRules.clear(); - userStylesheet.apply(); - } - - commit(commitNow) { - if ( commitNow ) { - this.commitTimer.clear(); - this.commitNow(); - } else { - this.commitTimer.start(); - } - } - - addCSSRule(selectors, declarations, details = {}) { - if ( selectors === undefined ) { return; } - const selectorsStr = Array.isArray(selectors) - ? selectors.join(',\n') - : selectors; - if ( selectorsStr.length === 0 ) { return; } - const entry = { - selectors: selectorsStr, - declarations, - lazy: details.lazy === true, - injected: details.injected === true - }; - this.addedCSSRules.add(entry); - this.filterset.add(entry); - if ( - this.disabled === false && - entry.lazy !== true && - entry.injected !== true - ) { - vAPI.userStylesheet.add(selectorsStr + '\n{' + declarations + '}'); - } - this.commit(); - if ( details.silent !== true && this.hasListeners() ) { - this.triggerListeners({ - declarative: [ [ selectorsStr, declarations ] ] - }); - } - } - - exceptCSSRules(exceptions) { - if ( exceptions.length === 0 ) { return; } - this.exceptedCSSRules.push(...exceptions); - if ( this.hasListeners() ) { - this.triggerListeners({ exceptions }); - } - } - - addListener(listener) { - if ( this.listeners.indexOf(listener) !== -1 ) { return; } - this.listeners.push(listener); - } - - removeListener(listener) { - const pos = this.listeners.indexOf(listener); - if ( pos === -1 ) { return; } - this.listeners.splice(pos, 1); - } - - hasListeners() { - return this.listeners.length !== 0; - } - - triggerListeners(changes) { - for ( const listener of this.listeners ) { - listener.onFiltersetChanged(changes); - } - } - - excludeNode(node) { - this.excludedNodeSet.add(node); - this.unhideNode(node); - } - - unexcludeNode(node) { - this.excludedNodeSet.delete(node); - } - - hideNode(node) { - if ( this.excludedNodeSet.has(node) ) { return; } - if ( this.hideNodeAttr === undefined ) { return; } - node.setAttribute(this.hideNodeAttr, ''); - if ( this.hideNodeStyleSheetInjected ) { return; } - this.hideNodeStyleSheetInjected = true; - this.addCSSRule( - `[${this.hideNodeAttr}]`, - 'display:none!important;', - { silent: true } - ); - } - - unhideNode(node) { - if ( this.hideNodeAttr === undefined ) { return; } - node.removeAttribute(this.hideNodeAttr); - } - - toggle(state, callback) { - if ( state === undefined ) { state = this.disabled; } - if ( state !== this.disabled ) { return; } - this.disabled = !state; - const userStylesheet = vAPI.userStylesheet; - for ( const entry of this.filterset ) { - const rule = `${entry.selectors}\n{${entry.declarations}}`; - if ( this.disabled ) { - userStylesheet.remove(rule); - } else { - userStylesheet.add(rule); - } - } - userStylesheet.apply(callback); - } - - getAllSelectors_(all) { - const out = { - declarative: [], - exceptions: this.exceptedCSSRules, - }; - for ( const entry of this.filterset ) { - let selectors = entry.selectors; - if ( all !== true && this.hideNodeAttr !== undefined ) { - selectors = selectors - .replace(`[${this.hideNodeAttr}]`, '') - .replace(/^,\n|,\n$/gm, ''); - if ( selectors === '' ) { continue; } - } - out.declarative.push([ selectors, entry.declarations ]); - } - return out; - } - - getFilteredElementCount() { - const details = this.getAllSelectors_(true); - if ( Array.isArray(details.declarative) === false ) { return 0; } - const selectors = details.declarative.map(entry => entry[0]); - if ( selectors.length === 0 ) { return 0; } - return document.querySelectorAll(selectors.join(',\n')).length; - } - - getAllSelectors() { - return this.getAllSelectors_(false); - } -}; - -/******************************************************************************/ -/******************************************************************************/ - -} -// <<<<<<<< end of HUGE-IF-BLOCK - - - - - - - - -/******************************************************************************* - - DO NOT: - - Remove the following code - - Add code beyond the following code - Reason: - - https://github.com/gorhill/uBlock/pull/3721 - - uBO never uses the return value from injected content scripts - -**/ - -void 0; diff --git a/platform/firefox/vapi-usercss.js b/platform/firefox/vapi-usercss.js deleted file mode 100644 index de4946c7da281..0000000000000 --- a/platform/firefox/vapi-usercss.js +++ /dev/null @@ -1,48 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2018 Raymond Hill - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see {http://www.gnu.org/licenses/}. - - Home: https://github.com/gorhill/uBlock -*/ - -'use strict'; - -// User stylesheets are always supported with Firefox/webext . - -if ( typeof vAPI === 'object' ) { - vAPI.supportsUserStylesheets = true; -} - - - - - - - - -/******************************************************************************* - - DO NOT: - - Remove the following code - - Add code beyond the following code - Reason: - - https://github.com/gorhill/uBlock/pull/3721 - - uBO never uses the return value from injected content scripts - -**/ - -void 0; diff --git a/platform/webext/vapi-usercss.js b/platform/webext/vapi-usercss.js deleted file mode 100644 index 6ec8ebb9224a7..0000000000000 --- a/platform/webext/vapi-usercss.js +++ /dev/null @@ -1,54 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2018 Raymond Hill - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see {http://www.gnu.org/licenses/}. - - Home: https://github.com/gorhill/uBlock -*/ - -'use strict'; - -// This file can be replaced by platform-specific code. If a platform is -// known to NOT support user stylsheets, vAPI.supportsUserStylesheets can be -// set to `false`. - -// Chromium 66 and above supports user stylesheets: -// https://github.com/gorhill/uBlock/issues/3588 - -if ( typeof vAPI === 'object' ) { - vAPI.supportsUserStylesheets = - /\bChrom(?:e|ium)\/(?:6[6789]|[789]|1\d\d)|\bFirefox\/\d/.test(navigator.userAgent); -} - - - - - - - - -/******************************************************************************* - - DO NOT: - - Remove the following code - - Add code beyond the following code - Reason: - - https://github.com/gorhill/uBlock/pull/3721 - - uBO never uses the return value from injected content scripts - -**/ - -void 0; diff --git a/src/js/contentscript.js b/src/js/contentscript.js index c2b3506b86f9b..7cbc123817e0d 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -114,6 +114,38 @@ if ( typeof vAPI === 'object' && !vAPI.contentScript ) { vAPI.contentScript = true; +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + +vAPI.userStylesheet = { + added: new Set(), + removed: new Set(), + apply: function(callback) { + if ( this.added.size === 0 && this.removed.size === 0 ) { return; } + vAPI.messaging.send('vapi', { + what: 'userCSS', + add: Array.from(this.added), + remove: Array.from(this.removed), + }).then(( ) => { + if ( callback instanceof Function === false ) { return; } + callback(); + }); + this.added.clear(); + this.removed.clear(); + }, + add: function(cssText, now) { + if ( cssText === '' ) { return; } + this.added.add(cssText); + if ( now ) { this.apply(); } + }, + remove: function(cssText, now) { + if ( cssText === '' ) { return; } + this.removed.add(cssText); + if ( now ) { this.apply(); } + } +}; + /******************************************************************************/ /******************************************************************************/ /******************************************************************************* @@ -141,13 +173,12 @@ vAPI.contentScript = true; // https://github.com/gorhill/uBlock/issues/2147 -vAPI.SafeAnimationFrame = function(callback) { - this.fid = this.tid = undefined; - this.callback = callback; -}; - -vAPI.SafeAnimationFrame.prototype = { - start: function(delay) { +vAPI.SafeAnimationFrame = class { + constructor(callback) { + this.fid = this.tid = undefined; + this.callback = callback; + } + start(delay) { if ( self.vAPI instanceof Object === false ) { return; } if ( delay === undefined ) { if ( this.fid === undefined ) { @@ -161,8 +192,8 @@ vAPI.SafeAnimationFrame.prototype = { if ( this.fid === undefined && this.tid === undefined ) { this.tid = vAPI.setTimeout(( ) => { this.macroToMicro(); }, delay); } - }, - clear: function() { + } + clear() { if ( this.fid !== undefined ) { cancelAnimationFrame(this.fid); this.fid = undefined; @@ -171,27 +202,27 @@ vAPI.SafeAnimationFrame.prototype = { clearTimeout(this.tid); this.tid = undefined; } - }, - macroToMicro: function() { + } + macroToMicro() { this.tid = undefined; this.start(); - }, - onRAF: function() { + } + onRAF() { if ( this.tid !== undefined ) { clearTimeout(this.tid); this.tid = undefined; } this.fid = undefined; this.callback(); - }, - onSTO: function() { + } + onSTO() { if ( this.fid !== undefined ) { cancelAnimationFrame(this.fid); this.fid = undefined; } this.tid = undefined; this.callback(); - }, + } }; /******************************************************************************/ @@ -267,7 +298,9 @@ vAPI.SafeAnimationFrame.prototype = { /******************************************************************************/ /******************************************************************************/ -vAPI.domWatcher = (( ) => { +// vAPI.domWatcher + +{ vAPI.domMutationTime = Date.now(); const addedNodeLists = []; @@ -406,8 +439,8 @@ vAPI.domWatcher = (( ) => { startMutationObserver(); }; - return { start, addListener, removeListener }; -})(); + vAPI.domWatcher = { start, addListener, removeListener }; +} /******************************************************************************/ /******************************************************************************/ @@ -436,15 +469,11 @@ vAPI.injectScriptlet = function(doc, text) { The DOM filterer is the heart of uBO's cosmetic filtering. - DOMBaseFilterer: platform-specific - | - | - +---- DOMFilterer: adds procedural cosmetic filtering + DOMFilterer: adds procedural cosmetic filtering */ -vAPI.DOMFilterer = (function() { - +{ // 'P' stands for 'Procedural' const PSelectorHasTextTask = class { @@ -837,9 +866,19 @@ vAPI.DOMFilterer = (function() { } }; - const DOMFilterer = class extends vAPI.DOMFilterer { + vAPI.DOMFilterer = class { constructor() { - super(); + this.commitTimer = new vAPI.SafeAnimationFrame( + ( ) => { this.commitNow(); } + ); + this.domIsReady = document.readyState !== 'loading'; + this.disabled = false; + this.listeners = []; + this.filterset = new Set(); + this.excludedNodeSet = new WeakSet(); + this.addedCSSRules = new Set(); + this.exceptedCSSRules = []; + this.reOnlySelectors = /\n\{[^\n]+/g; this.exceptions = []; this.proceduralFilterer = new DOMProceduralFilterer(this); this.hideNodeAttr = undefined; @@ -847,13 +886,177 @@ vAPI.DOMFilterer = (function() { if ( vAPI.domWatcher instanceof Object ) { vAPI.domWatcher.addListener(this); } + // https://github.com/uBlockOrigin/uBlock-issues/issues/167 + // By the time the DOMContentLoaded is fired, the content script might + // have been disconnected from the background page. Unclear why this + // would happen, so far seems to be a Chromium-specific behavior at + // launch time. + if ( this.domIsReady !== true ) { + document.addEventListener('DOMContentLoaded', ( ) => { + if ( vAPI instanceof Object === false ) { return; } + this.domIsReady = true; + this.commit(); + }); + } + } + + addCSSRule(selectors, declarations, details = {}) { + if ( selectors === undefined ) { return; } + const selectorsStr = Array.isArray(selectors) + ? selectors.join(',\n') + : selectors; + if ( selectorsStr.length === 0 ) { return; } + const entry = { + selectors: selectorsStr, + declarations, + lazy: details.lazy === true, + injected: details.injected === true + }; + this.addedCSSRules.add(entry); + this.filterset.add(entry); + if ( + this.disabled === false && + entry.lazy !== true && + entry.injected !== true + ) { + vAPI.userStylesheet.add(`${selectorsStr}\n{${declarations}}`); + } + this.commit(); + if ( details.silent !== true && this.hasListeners() ) { + this.triggerListeners({ + declarative: [ [ selectorsStr, declarations ] ] + }); + } + } + + exceptCSSRules(exceptions) { + if ( exceptions.length === 0 ) { return; } + this.exceptedCSSRules.push(...exceptions); + if ( this.hasListeners() ) { + this.triggerListeners({ exceptions }); + } + } + + addListener(listener) { + if ( this.listeners.indexOf(listener) !== -1 ) { return; } + this.listeners.push(listener); + } + + removeListener(listener) { + const pos = this.listeners.indexOf(listener); + if ( pos === -1 ) { return; } + this.listeners.splice(pos, 1); + } + + hasListeners() { + return this.listeners.length !== 0; + } + + triggerListeners(changes) { + for ( const listener of this.listeners ) { + listener.onFiltersetChanged(changes); + } + } + + excludeNode(node) { + this.excludedNodeSet.add(node); + this.unhideNode(node); + } + + unexcludeNode(node) { + this.excludedNodeSet.delete(node); + } + + hideNode(node) { + if ( this.excludedNodeSet.has(node) ) { return; } + if ( this.hideNodeAttr === undefined ) { return; } + node.setAttribute(this.hideNodeAttr, ''); + if ( this.hideNodeStyleSheetInjected ) { return; } + this.hideNodeStyleSheetInjected = true; + this.addCSSRule( + `[${this.hideNodeAttr}]`, + 'display:none!important;', + { silent: true } + ); + } + + unhideNode(node) { + if ( this.hideNodeAttr === undefined ) { return; } + node.removeAttribute(this.hideNodeAttr); + } + + toggle(state, callback) { + if ( state === undefined ) { state = this.disabled; } + if ( state !== this.disabled ) { return; } + this.disabled = !state; + const userStylesheet = vAPI.userStylesheet; + for ( const entry of this.filterset ) { + const rule = `${entry.selectors}\n{${entry.declarations}}`; + if ( this.disabled ) { + userStylesheet.remove(rule); + } else { + userStylesheet.add(rule); + } + } + userStylesheet.apply(callback); + } + + getAllSelectors_(all) { + const out = { + declarative: [], + exceptions: this.exceptedCSSRules, + }; + for ( const entry of this.filterset ) { + let selectors = entry.selectors; + if ( all !== true && this.hideNodeAttr !== undefined ) { + selectors = selectors + .replace(`[${this.hideNodeAttr}]`, '') + .replace(/^,\n|,\n$/gm, ''); + if ( selectors === '' ) { continue; } + } + out.declarative.push([ selectors, entry.declarations ]); + } + return out; } + // Here we will deal with: + // - Injecting low priority user styles; + // - Notifying listeners about changed filterset. + // https://www.reddit.com/r/uBlockOrigin/comments/9jj0y1/no_longer_blocking_ads/ + // Ensure vAPI is still valid -- it can go away by the time we are + // called, since the port could be force-disconnected from the main + // process. Another approach would be to have vAPI.SafeAnimationFrame + // register a shutdown job: to evaluate. For now I will keep the fix + // trivial. commitNow() { - super.commitNow(); + this.commitTimer.clear(); + if ( vAPI instanceof Object === false ) { return; } + const userStylesheet = vAPI.userStylesheet; + for ( const entry of this.addedCSSRules ) { + if ( + this.disabled === false && + entry.lazy && + entry.injected === false + ) { + userStylesheet.add( + entry.selectors + '\n{' + entry.declarations + '}' + ); + } + } + this.addedCSSRules.clear(); + userStylesheet.apply(); this.proceduralFilterer.commitNow(); } + commit(commitNow) { + if ( commitNow ) { + this.commitTimer.clear(); + this.commitNow(); + } else { + this.commitTimer.start(); + } + } + addProceduralSelectors(aa) { this.proceduralFilterer.addProceduralSelectors(aa); } @@ -863,8 +1066,10 @@ vAPI.DOMFilterer = (function() { } getAllSelectors() { - const out = super.getAllSelectors(); - out.procedural = Array.from(this.proceduralFilterer.selectors.values()); + const out = this.getAllSelectors_(false); + out.procedural = Array.from( + this.proceduralFilterer.selectors.values() + ); return out; } @@ -872,34 +1077,34 @@ vAPI.DOMFilterer = (function() { return this.exceptions.join(',\n'); } + getFilteredElementCount() { + const details = this.getAllSelectors_(true); + if ( Array.isArray(details.declarative) === false ) { return 0; } + const selectors = details.declarative.map(entry => entry[0]); + if ( selectors.length === 0 ) { return 0; } + return document.querySelectorAll(selectors.join(',\n')).length; + } + onDOMCreated() { - if ( super.onDOMCreated instanceof Function ) { - super.onDOMCreated(); - } this.proceduralFilterer.onDOMCreated(); } onDOMChanged() { - if ( super.onDOMChanged instanceof Function ) { - super.onDOMChanged.apply(this, arguments); - } this.proceduralFilterer.onDOMChanged.apply( this.proceduralFilterer, arguments ); } }; - - return DOMFilterer; -})(); - -vAPI.domFilterer = new vAPI.DOMFilterer(); +} /******************************************************************************/ /******************************************************************************/ /******************************************************************************/ -vAPI.domCollapser = (function() { +// vAPI.domCollapser + +{ const messaging = vAPI.messaging; const toCollapse = new Map(); const src1stProps = { @@ -1136,14 +1341,16 @@ vAPI.domCollapser = (function() { vAPI.domWatcher.addListener(domWatcherInterface); } - return { add, addMany, addIFrame, addIFrames, process }; -})(); + vAPI.domCollapser = { add, addMany, addIFrame, addIFrames, process }; +} /******************************************************************************/ /******************************************************************************/ /******************************************************************************/ -vAPI.domSurveyor = (function() { +// vAPI.domSurveyor + +{ const messaging = vAPI.messaging; const queriedIds = new Set(); const queriedClasses = new Set(); @@ -1402,18 +1609,18 @@ vAPI.domSurveyor = (function() { vAPI.domWatcher.addListener(domWatcherInterface); }; - return { start }; -})(); + vAPI.domSurveyor = { start }; +} /******************************************************************************/ /******************************************************************************/ /******************************************************************************/ -// Bootstrapping allows all components of the content script to be launched -// if/when needed. - -vAPI.bootstrap = (function() { +// vAPI.bootstrap: +// Bootstrapping allows all components of the content script +// to be launched if/when needed. +{ const bootstrapPhase2 = function() { // This can happen on Firefox. For instance: // https://github.com/gorhill/uBlock/issues/1893 @@ -1488,7 +1695,7 @@ vAPI.bootstrap = (function() { vAPI.domFilterer = null; vAPI.domSurveyor = null; } else { - const domFilterer = vAPI.domFilterer; + const domFilterer = vAPI.domFilterer = new vAPI.DOMFilterer(); if ( response.noGenericCosmeticFiltering || cfeDetails.noDOMSurveying ) { vAPI.domSurveyor = null; } @@ -1555,7 +1762,7 @@ vAPI.bootstrap = (function() { } }; - return function() { + vAPI.bootstrap = function() { vAPI.messaging.send('contentscript', { what: 'retrieveContentScriptParameters', url: window.location.href, @@ -1565,7 +1772,7 @@ vAPI.bootstrap = (function() { bootstrapPhase1(response); }); }; -})(); +} // This starts bootstrap process. vAPI.bootstrap(); diff --git a/tools/make-chromium.sh b/tools/make-chromium.sh index 3307fbca55578..e1f6e5c373518 100755 --- a/tools/make-chromium.sh +++ b/tools/make-chromium.sh @@ -11,19 +11,6 @@ mkdir -p $DES echo "*** uBlock0.chromium: copying common files" bash ./tools/copy-common-files.sh $DES -echo "*** uBlock0.chromium: concatenating content scripts" -cat $DES/js/vapi-usercss.js > /tmp/contentscript.js -echo >> /tmp/contentscript.js -grep -v "^'use strict';$" $DES/js/vapi-usercss.real.js >> /tmp/contentscript.js -echo >> /tmp/contentscript.js -grep -v "^'use strict';$" $DES/js/vapi-usercss.pseudo.js >> /tmp/contentscript.js -echo >> /tmp/contentscript.js -grep -v "^'use strict';$" $DES/js/contentscript.js >> /tmp/contentscript.js -mv /tmp/contentscript.js $DES/js/contentscript.js -rm $DES/js/vapi-usercss.js -rm $DES/js/vapi-usercss.real.js -rm $DES/js/vapi-usercss.pseudo.js - # Chrome store-specific cp -R $DES/_locales/nb $DES/_locales/no diff --git a/tools/make-firefox.sh b/tools/make-firefox.sh index 6bca88b7c9d44..d1708cdd45973 100755 --- a/tools/make-firefox.sh +++ b/tools/make-firefox.sh @@ -16,20 +16,8 @@ cp -R $DES/_locales/nb $DES/_locales/no cp platform/firefox/manifest.json $DES/ cp platform/firefox/webext.js $DES/js/ -cp platform/firefox/vapi-usercss.js $DES/js/ cp platform/firefox/vapi-webrequest.js $DES/js/ -echo "*** uBlock0.firefox: concatenating content scripts" -cat $DES/js/vapi-usercss.js > /tmp/contentscript.js -echo >> /tmp/contentscript.js -grep -v "^'use strict';$" $DES/js/vapi-usercss.real.js >> /tmp/contentscript.js -echo >> /tmp/contentscript.js -grep -v "^'use strict';$" $DES/js/contentscript.js >> /tmp/contentscript.js -mv /tmp/contentscript.js $DES/js/contentscript.js -rm $DES/js/vapi-usercss.js -rm $DES/js/vapi-usercss.real.js -rm $DES/js/vapi-usercss.pseudo.js - # Firefox/webext-specific rm $DES/img/icon_128.png diff --git a/tools/make-opera.sh b/tools/make-opera.sh index 73219157dbd9a..8ce392fac0cfe 100755 --- a/tools/make-opera.sh +++ b/tools/make-opera.sh @@ -11,19 +11,6 @@ mkdir -p $DES echo "*** uBlock0.opera: copying common files" bash ./tools/copy-common-files.sh $DES -echo "*** uBlock0.opera: concatenating content scripts" -cat $DES/js/vapi-usercss.js > /tmp/contentscript.js -echo >> /tmp/contentscript.js -grep -v "^'use strict';$" $DES/js/vapi-usercss.real.js >> /tmp/contentscript.js -echo >> /tmp/contentscript.js -grep -v "^'use strict';$" $DES/js/vapi-usercss.pseudo.js >> /tmp/contentscript.js -echo >> /tmp/contentscript.js -grep -v "^'use strict';$" $DES/js/contentscript.js >> /tmp/contentscript.js -mv /tmp/contentscript.js $DES/js/contentscript.js -rm $DES/js/vapi-usercss.js -rm $DES/js/vapi-usercss.real.js -rm $DES/js/vapi-usercss.pseudo.js - # Opera-specific cp platform/opera/manifest.json $DES/ rm -r $DES/_locales/az diff --git a/tools/make-webext.sh b/tools/make-webext.sh index 601afc4a8ac7a..80e66a0f47c37 100755 --- a/tools/make-webext.sh +++ b/tools/make-webext.sh @@ -17,28 +17,13 @@ bash ./tools/copy-common-files.sh $DES cp -R $DES/_locales/nb $DES/_locales/no cp platform/webext/manifest.json $DES/ -cp platform/webext/vapi-usercss.js $DES/js/ # https://github.com/uBlockOrigin/uBlock-issues/issues/407 echo "*** uBlock0.webext: concatenating vapi-webrequest.js" cat platform/chromium/vapi-webrequest.js > /tmp/vapi-webrequest.js -echo >> /tmp/contentscript.js grep -v "^'use strict';$" platform/firefox/vapi-webrequest.js >> /tmp/vapi-webrequest.js mv /tmp/vapi-webrequest.js $DES/js/vapi-webrequest.js -echo "*** uBlock0.webext: concatenating content scripts" -cat $DES/js/vapi-usercss.js > /tmp/contentscript.js -echo >> /tmp/contentscript.js -grep -v "^'use strict';$" $DES/js/vapi-usercss.real.js >> /tmp/contentscript.js -echo >> /tmp/contentscript.js -grep -v "^'use strict';$" $DES/js/vapi-usercss.pseudo.js >> /tmp/contentscript.js -echo >> /tmp/contentscript.js -grep -v "^'use strict';$" $DES/js/contentscript.js >> /tmp/contentscript.js -mv /tmp/contentscript.js $DES/js/contentscript.js -rm $DES/js/vapi-usercss.js -rm $DES/js/vapi-usercss.real.js -rm $DES/js/vapi-usercss.pseudo.js - echo "*** uBlock0.webext: Generating meta..." python3 tools/make-webext-meta.py $DES/ From 39190ff4699da01b8feb8526fd29287a55a14a0b Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Fri, 24 Jul 2020 12:32:47 -0400 Subject: [PATCH 06/38] Add Chromium detection for Chromium-based MS Edge Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/1174 --- platform/chromium/vapi-common.js | 30 +++++++----------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/platform/chromium/vapi-common.js b/platform/chromium/vapi-common.js index 30e09c577d4af..e8de131d2d47d 100644 --- a/platform/chromium/vapi-common.js +++ b/platform/chromium/vapi-common.js @@ -75,8 +75,7 @@ vAPI.webextFlavor = { dispatch(); }); if ( browser.runtime.getURL('').startsWith('moz-extension://') ) { - soup.add('mozilla') - .add('firefox') + soup.add('firefox') .add('user_stylesheet') .add('html_filtering'); flavor.major = 60; @@ -85,29 +84,14 @@ vAPI.webextFlavor = { } // Synchronous -- order of tests is important - let match; - if ( (match = /\bEdge\/(\d+)/.exec(ua)) !== null ) { - flavor.major = parseInt(match[1], 10) || 0; - soup.add('microsoft').add('edge'); - } else if ( (match = /\bOPR\/(\d+)/.exec(ua)) !== null ) { - const reEx = /\bChrom(?:e|ium)\/([\d.]+)/; - if ( reEx.test(ua) ) { match = reEx.exec(ua); } - flavor.major = parseInt(match[1], 10) || 0; - soup.add('opera').add('chromium'); - } else if ( (match = /\bChromium\/(\d+)/.exec(ua)) !== null ) { - flavor.major = parseInt(match[1], 10) || 0; + const match = /\bChrom(?:e|ium)\/([\d.]+)/.exec(ua); + if ( match !== null ) { soup.add('chromium'); - } else if ( (match = /\bChrome\/(\d+)/.exec(ua)) !== null ) { - flavor.major = parseInt(match[1], 10) || 0; - soup.add('google').add('chromium'); - } else if ( (match = /\bSafari\/(\d+)/.exec(ua)) !== null ) { flavor.major = parseInt(match[1], 10) || 0; - soup.add('apple').add('safari'); - } - - // https://github.com/gorhill/uBlock/issues/3588 - if ( soup.has('chromium') && flavor.major >= 66 ) { - soup.add('user_stylesheet'); + // https://github.com/gorhill/uBlock/issues/3588 + if ( flavor.major >= 66 ) { + soup.add('user_stylesheet'); + } } // Don't starve potential listeners From 17e5a150fb8d1969bac1b063d94220a321d3c210 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Fri, 24 Jul 2020 12:38:49 -0400 Subject: [PATCH 07/38] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 074530a91c8a4..3cf2f876dab9b 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.28.5.5 +1.28.5.6 From 72cfc3218a5e6255efea9dc98b603fe537a6d800 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Fri, 24 Jul 2020 12:42:21 -0400 Subject: [PATCH 08/38] Import translation work from https://crowdin.com/project/ublock --- src/_locales/nl/messages.json | 4 ++-- src/_locales/pl/messages.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/_locales/nl/messages.json b/src/_locales/nl/messages.json index e248909384774..404a4fe90497e 100644 --- a/src/_locales/nl/messages.json +++ b/src/_locales/nl/messages.json @@ -112,11 +112,11 @@ "description": "English: Click to open the dashboard" }, "popupTipZapper": { - "message": "Element­wisser­modus openen", + "message": "Element­wisser­modus openen", "description": "Tooltip for the element-zapper icon in the popup panel" }, "popupTipPicker": { - "message": "Element­kiezer­modus openen", + "message": "Element­kiezer­modus openen", "description": "English: Enter element picker mode" }, "popupTipLog": { diff --git a/src/_locales/pl/messages.json b/src/_locales/pl/messages.json index 2cab196c3159e..dc63a633f8cef 100644 --- a/src/_locales/pl/messages.json +++ b/src/_locales/pl/messages.json @@ -552,7 +552,7 @@ "description": "English: dynamic rule syntax and full documentation." }, "whitelistPrompt": { - "message": "Wytyczne wyjątków nakazują, na których stronach uBlock Origin powinien zostać wyłączony. Jeden wpis na linię. Nieprawidłowe wytyczne zostaną bez powiadomienia zignorowane i wykomentowane.", + "message": "Dyrektywy białej listy wskazują, na których stronach uBlock Origin powinien zostać wyłączony. Jeden wpis na linię. Nieprawidłowe wpisy zostaną bez powiadomienia zignorowane i wykomentowane.", "description": "The name of the trusted sites pane." }, "whitelistImport": { @@ -564,7 +564,7 @@ "description": "English: Export" }, "whitelistExportFilename": { - "message": "my-ublock-whitelist_{{datetime}}.txt", + "message": "ublock-biała-lista_{{datetime}}.txt", "description": "The default filename to use for import/export purpose" }, "whitelistApply": { From 779fde8f3a70ad3900ff2f4a97d751ad9319628e Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Fri, 24 Jul 2020 12:51:06 -0400 Subject: [PATCH 09/38] Make Firefox dev build auto-update --- dist/firefox/updates.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index a08cf295e84b3..c5214b41876ec 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,10 +3,10 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.28.5.5", + "version": "1.28.5.6", "browser_specific_settings": { "gecko": { "strict_min_version": "55" } }, - "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b5", - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b5/uBlock0_1.28.5b5.firefox.signed.xpi" + "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b6", + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b6/uBlock0_1.28.5b6.firefox.signed.xpi" } ] } From 3b72c7cb046e6880149fc2adf669b1f77d822d9a Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Fri, 24 Jul 2020 18:50:12 -0400 Subject: [PATCH 10/38] Ensure `about:` frames use proper origin Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/688 `about:` frames need to lookup and use the inherited origin from their parent browsing context for proper lookup of cosmetic filters. --- src/js/contentscript.js | 24 ++++++++++++++++++++++-- src/js/messaging.js | 12 ++++++++---- src/js/scriptlets/cosmetic-logger.js | 6 ++++-- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/js/contentscript.js b/src/js/contentscript.js index 7cbc123817e0d..850449734df55 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -118,6 +118,26 @@ vAPI.contentScript = true; /******************************************************************************/ /******************************************************************************/ +// https://github.com/uBlockOrigin/uBlock-issues/issues/688#issuecomment-663657508 +{ + let location = self.location; + if ( location.protocol === 'about:' ) { + try { + let context = self; + do { + context = context.parent; + location = context.location; + } while ( context !== self.top && location.protocol === 'about:' ); + } catch(ex) { + } + } + vAPI.pageLocation = location; +} + +/******************************************************************************/ +/******************************************************************************/ +/******************************************************************************/ + vAPI.userStylesheet = { added: new Set(), removed: new Set(), @@ -1765,8 +1785,8 @@ vAPI.injectScriptlet = function(doc, text) { vAPI.bootstrap = function() { vAPI.messaging.send('contentscript', { what: 'retrieveContentScriptParameters', - url: window.location.href, - isRootFrame: window === window.top, + url: vAPI.pageLocation.href, + isRootFrame: self === self.top, charset: document.characterSet, }).then(response => { bootstrapPhase1(response); diff --git a/src/js/messaging.js b/src/js/messaging.js index d0e18a1f2a2bb..a8d6128f08c73 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -524,11 +524,15 @@ const µb = µBlock; const retrieveContentScriptParameters = function(senderDetails, request) { if ( µb.readyToFilter !== true ) { return; } - const { url, tabId, frameId } = senderDetails; - if ( url === undefined || tabId === undefined || frameId === undefined ) { + const { url: senderURL, tabId, frameId } = senderDetails; + if ( + tabId === undefined || + frameId === undefined || + senderURL === undefined || + senderURL !== request.url && senderURL.startsWith('about:') === false + ) { return; } - if ( request.url !== url ) { return; } const pageStore = µb.pageStoreFromTabId(tabId); if ( pageStore === null || pageStore.getNetFilteringSwitch() === false ) { return; @@ -714,7 +718,7 @@ const onMessage = function(request, sender, callback) { xhr.responseType = 'text'; xhr.onload = function() { this.onload = null; - var i18n = { + const i18n = { bidi_dir: document.body.getAttribute('dir'), create: vAPI.i18n('pickerCreate'), pick: vAPI.i18n('pickerPick'), diff --git a/src/js/scriptlets/cosmetic-logger.js b/src/js/scriptlets/cosmetic-logger.js index 57ee4723a17c0..a096a331c8e72 100644 --- a/src/js/scriptlets/cosmetic-logger.js +++ b/src/js/scriptlets/cosmetic-logger.js @@ -211,10 +211,12 @@ const processTimer = new vAPI.SafeAnimationFrame(( ) => { if ( toLog.length === 0 ) { return; } + const location = vAPI.pageLocation || self.location; + vAPI.messaging.send('scriptlets', { what: 'logCosmeticFilteringData', - frameURL: window.location.href, - frameHostname: window.location.hostname, + frameURL: location.href, + frameHostname: location.hostname, matchedSelectors: toLog, }); //console.timeEnd('dom logger/scanning for matches'); From e98ea7ea9b7c654c82a1e2f258b660f168b99ef0 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Fri, 24 Jul 2020 19:08:48 -0400 Subject: [PATCH 11/38] Instantiate procedural filterer instance on demand only The procedural filterer will be instantiated only when needed, i.e. only when there are actual procedural filters to enforce. --- src/js/contentscript.js | 78 +++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/src/js/contentscript.js b/src/js/contentscript.js index 850449734df55..f9b0950676ead 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -762,6 +762,9 @@ vAPI.injectScriptlet = function(doc, text) { this.mustApplySelectors = false; this.selectors = new Map(); this.hiddenNodes = new Set(); + if ( vAPI.domWatcher instanceof Object ) { + vAPI.domWatcher.addListener(this); + } } addProceduralSelectors(aa) { @@ -873,7 +876,7 @@ vAPI.injectScriptlet = function(doc, text) { onDOMCreated() { this.domIsReady = true; - this.domFilterer.commitNow(); + this.domFilterer.commit(); } onDOMChanged(addedNodes, removedNodes) { @@ -900,12 +903,9 @@ vAPI.injectScriptlet = function(doc, text) { this.exceptedCSSRules = []; this.reOnlySelectors = /\n\{[^\n]+/g; this.exceptions = []; - this.proceduralFilterer = new DOMProceduralFilterer(this); + this.proceduralFilterer = null; this.hideNodeAttr = undefined; this.hideNodeStyleSheetInjected = false; - if ( vAPI.domWatcher instanceof Object ) { - vAPI.domWatcher.addListener(this); - } // https://github.com/uBlockOrigin/uBlock-issues/issues/167 // By the time the DOMContentLoaded is fired, the content script might // have been disconnected from the background page. Unclear why this @@ -1059,13 +1059,15 @@ vAPI.injectScriptlet = function(doc, text) { entry.injected === false ) { userStylesheet.add( - entry.selectors + '\n{' + entry.declarations + '}' + `${entry.selectors}\n{${entry.declarations}}` ); } } this.addedCSSRules.clear(); userStylesheet.apply(); - this.proceduralFilterer.commitNow(); + if ( this.proceduralFilterer instanceof Object ) { + this.proceduralFilterer.commitNow(); + } } commit(commitNow) { @@ -1077,19 +1079,27 @@ vAPI.injectScriptlet = function(doc, text) { } } + proceduralFiltererInstance() { + if ( this.proceduralFilterer instanceof Object === false ) { + this.proceduralFilterer = new DOMProceduralFilterer(this); + } + return this.proceduralFilterer; + } + addProceduralSelectors(aa) { - this.proceduralFilterer.addProceduralSelectors(aa); + if ( aa.length === 0 ) { return; } + this.proceduralFiltererInstance().addProceduralSelectors(aa); } createProceduralFilter(o) { - return this.proceduralFilterer.createProceduralFilter(o); + return this.proceduralFiltererInstance().createProceduralFilter(o); } getAllSelectors() { const out = this.getAllSelectors_(false); - out.procedural = Array.from( - this.proceduralFilterer.selectors.values() - ); + out.procedural = this.proceduralFilterer instanceof Object + ? Array.from(this.proceduralFilterer.selectors.values()) + : []; return out; } @@ -1104,17 +1114,6 @@ vAPI.injectScriptlet = function(doc, text) { if ( selectors.length === 0 ) { return 0; } return document.querySelectorAll(selectors.join(',\n')).length; } - - onDOMCreated() { - this.proceduralFilterer.onDOMCreated(); - } - - onDOMChanged() { - this.proceduralFilterer.onDOMChanged.apply( - this.proceduralFilterer, - arguments - ); - } }; } @@ -1304,6 +1303,24 @@ vAPI.injectScriptlet = function(doc, text) { } }; + const stop = function() { + document.removeEventListener('error', onResourceFailed, true); + if ( processTimer !== undefined ) { + clearTimeout(processTimer); + } + if ( vAPI.domWatcher instanceof Object ) { + vAPI.domWatcher.removeListener(domWatcherInterface); + } + vAPI.shutdown.remove(stop); + vAPI.domCollapser = null; + }; + + const start = function() { + if ( vAPI.domWatcher instanceof Object ) { + vAPI.domWatcher.addListener(domWatcherInterface); + } + }; + const domWatcherInterface = { onDOMCreated: function() { if ( self.vAPI instanceof Object === false ) { return; } @@ -1334,12 +1351,7 @@ vAPI.injectScriptlet = function(doc, text) { document.addEventListener('error', onResourceFailed, true); - vAPI.shutdown.add(function() { - document.removeEventListener('error', onResourceFailed, true); - if ( processTimer !== undefined ) { - clearTimeout(processTimer); - } - }); + vAPI.shutdown.add(stop); }, onDOMChanged: function(addedNodes) { if ( addedNodes.length === 0 ) { return; } @@ -1357,11 +1369,7 @@ vAPI.injectScriptlet = function(doc, text) { } }; - if ( vAPI.domWatcher instanceof Object ) { - vAPI.domWatcher.addListener(domWatcherInterface); - } - - vAPI.domCollapser = { add, addMany, addIFrame, addIFrames, process }; + vAPI.domCollapser = { start }; } /******************************************************************************/ @@ -1711,6 +1719,8 @@ vAPI.injectScriptlet = function(doc, text) { return; } + vAPI.domCollapser.start(); + if ( response.noCosmeticFiltering ) { vAPI.domFilterer = null; vAPI.domSurveyor = null; From 4568ab89ffb2a1bb8df909c6d19ecc9b6cf551fc Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Fri, 24 Jul 2020 19:15:26 -0400 Subject: [PATCH 12/38] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 3cf2f876dab9b..db8aa3749a269 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.28.5.6 +1.28.5.7 From 44ecb302bffca59e3b27504fdf085b616101f19b Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Fri, 24 Jul 2020 19:20:28 -0400 Subject: [PATCH 13/38] Make Firefox dev build auto-update --- dist/firefox/updates.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index c5214b41876ec..198fa808dead2 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,10 +3,10 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.28.5.6", + "version": "1.28.5.7", "browser_specific_settings": { "gecko": { "strict_min_version": "55" } }, - "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b6", - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b6/uBlock0_1.28.5b6.firefox.signed.xpi" + "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b7", + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b7/uBlock0_1.28.5b7.firefox.signed.xpi" } ] } From 3df978ffd5f565ae8505bd992dd8920ad624fc7e Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 27 Jul 2020 10:20:11 -0400 Subject: [PATCH 14/38] Make usage of space more strict in network filter patterns Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/1118 Usage of space in network filter patterns will now be strictly interpreted as the filter being a hosts file entry. Usage of space in any other scenario will cause the pattern of the network filter to be rejected as erroneous. --- src/js/static-filtering-parser.js | 87 +++++++++++++++++++------------ 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/src/js/static-filtering-parser.js b/src/js/static-filtering-parser.js index 7610ea580d474..e1186ff59eff1 100644 --- a/src/js/static-filtering-parser.js +++ b/src/js/static-filtering-parser.js @@ -103,7 +103,9 @@ const Parser = class { this.extOptionsIterator = new ExtOptionsIterator(this); this.maxTokenLength = Number.MAX_SAFE_INTEGER; this.reIsLocalhostRedirect = /(?:0\.0\.0\.0|(?:broadcast|local)host|local|ip6-\w+)\b/; - this.reHostname = /^[^\x00-\x24\x26-\x29\x2B\x2C\x2F\x3A-\x5E\x60\x7B-\x7F]+/; + this.reHostname = /^[^\x00-\x24\x26-\x29\x2B\x2C\x2F\x3A-\x40\x5B-\x5E\x60\x7B-\x7F]+/; + this.reHostsSink = /^[\w-.:\[\]]+$/; + this.reHostsSource = /^[^\x00-\x24\x26-\x29\x2B\x2C\x2F\x3A-\x40\x5B-\x5E\x60\x7B-\x7F]+$/; this.reUnicodeChar = /[^\x00-\x7F]/; this.reUnicodeChars = /[^\x00-\x7F]/g; this.punycoder = new URL(self.location); @@ -507,36 +509,35 @@ const Parser = class { // Patterns with more than one space are dubious. { const { i, len } = this.patternSpan; + const noOptionsAnchor = this.optionsAnchorSpan.len === 0; let j = len; for (;;) { if ( j === 0 ) { break; } j -= 3; const bits = this.slices[i+j]; - if ( hasBits(bits, BITSpace) ) { break; } + if ( noOptionsAnchor && hasBits(bits, BITSpace) ) { break; } this.patternBits |= bits; } if ( j !== 0 ) { - let dubious = false; - for ( let k = this.patternSpan.i; k < j; k += 3 ) { - if ( hasNoBits(this.slices[k], BITSpace) ) { continue; } - this.patternBits |= BITSpace; - if ( this.interactive ) { - this.markSlices(this.patternSpan.i, j, BITError); - } - dubious = true; - break; - } - if ( dubious === false ) { + const sink = this.strFromSlices(this.patternSpan.i, j - 3); + if ( this.reHostsSink.test(sink) ) { this.patternSpan.i += j + 3; this.patternSpan.len -= j + 3; - if ( this.reIsLocalhostRedirect.test(this.getNetPattern()) ) { - this.flavorBits |= BITFlavorIgnore; - } if ( this.interactive ) { this.markSlices(0, this.patternSpan.i, BITIgnore); } + const source = this.getNetPattern(); + if ( this.reIsLocalhostRedirect.test(source) ) { + this.flavorBits |= BITFlavorIgnore; + } else if ( this.reHostsSource.test(source) === false ) { + this.patternBits |= BITError; + } + } else { + this.patternBits |= BITError; + } + if ( hasBits(this.patternBits, BITError) ) { + this.markSpan(this.patternSpan, BITError); } - // TODO: test again for regex? } } @@ -631,10 +632,8 @@ const Parser = class { this.markSpan(this.patternSpan, BITError); } } else if ( - this.patternIsDubious() || ( - this.patternHasUnicode() && - this.toASCII(true) === false - ) + this.patternIsDubious() === false && + this.toASCII(true) === false ) { this.markSlices( this.patternLeftAnchorSpan.i, @@ -909,21 +908,43 @@ const Parser = class { } // https://github.com/chrisaljoudi/uBlock/issues/1096 + // https://github.com/ryanbr/fanboy-adblock/issues/1384 // Examples of dubious filter content: // - Spaces characters - // - Single character other than `*` wildcard - // - Zero-length pattern with anchors - // https://github.com/ryanbr/fanboy-adblock/issues/1384 + // - Single character with no options + // - Wildcard(s) with no options + // - Zero-length pattern with no options patternIsDubious() { - return hasBits(this.patternBits, BITSpace) || ( - this.patternBits !== BITAsterisk && - this.optionsSpan.len === 0 && ( - this.patternSpan.len === 0 && - this.patternLeftAnchorSpan.len !== 0 || - this.patternSpan.len === 3 && - this.slices[this.patternSpan.i+2] === 1 - ) - ); + if ( hasBits(this.patternBits, BITError) ) { return true; } + if ( hasBits(this.patternBits, BITSpace) ) { + if ( this.interactive ) { + this.markSpan(this.patternSpan, BITError); + } + return true; + } + if ( this.patternSpan.len > 3 || this.optionsSpan.len !== 0 ) { + return false; + } + if ( + this.patternSpan.len === 3 && + this.slices[this.patternSpan.i+2] !== 1 && + hasNoBits(this.patternBits, BITAsterisk) + ) { + return false; + } + if ( this.interactive === false ) { return true; } + let l, r; + if ( this.patternSpan.len !== 0 ) { + l = this.patternSpan.i; + r = this.optionsAnchorSpan.i; + } else { + l = this.patternLeftAnchorSpan.i; + r = this.patternLeftAnchorSpan.len !== 0 + ? this.optionsAnchorSpan.i + : this.optionsSpan.i; + } + this.markSlices(l, r, BITError); + return true; } patternIsMatchAll() { From 7c63f252d0ec9c3f46e0155b37f25c7cccc289c7 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 27 Jul 2020 10:32:55 -0400 Subject: [PATCH 15/38] Add more cases to static filtering checklist --- .../static-filtering-parser-checklist.txt | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/tests/static-filtering-parser-checklist.txt b/docs/tests/static-filtering-parser-checklist.txt index dd3bf5ad0b925..85daa48dd7220 100644 --- a/docs/tests/static-filtering-parser-checklist.txt +++ b/docs/tests/static-filtering-parser-checklist.txt @@ -20,8 +20,17 @@ ! valid patterns a* -|* -||* +*$xhr +|*$xhr +|$xhr +||*$xhr +||$xhr +||*|$xhr + +! valid hosts file entries +:: ab +:: AB +:: ab # comment ! valid options $script,redirect=noop.js @@ -47,6 +56,17 @@ a | || $ +* +|* +||* +||*| + +! bad hosts file entries +:: a +:: ab/ +:: ab/ # comment +::/ ab +:: ab$ ! bad regex /(abc|def/$xhr From 5d9a3efd174d764e16bf0b850900435284c14d49 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 27 Jul 2020 10:40:27 -0400 Subject: [PATCH 16/38] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index db8aa3749a269..0a9bfe1536bd2 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.28.5.7 +1.28.5.8 From 7506c5dd70067b9644a3ba19463a84e9bae0cb10 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 27 Jul 2020 10:50:52 -0400 Subject: [PATCH 17/38] Make Firefox dev build auto-update --- dist/firefox/updates.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index 198fa808dead2..686abee8f5308 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,10 +3,10 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.28.5.7", + "version": "1.28.5.8", "browser_specific_settings": { "gecko": { "strict_min_version": "55" } }, - "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b7", - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b7/uBlock0_1.28.5b7.firefox.signed.xpi" + "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b8", + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b8/uBlock0_1.28.5b8.firefox.signed.xpi" } ] } From 9447829eb106c822be8d35cd50d43059bc87330d Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 27 Jul 2020 13:30:57 -0400 Subject: [PATCH 18/38] Fix regression causing logger to fail to report cosmetic filters Related commit: - https://github.com/gorhill/uBlock/commit/5c68867b92735931a791dfedf4ef9608cc364862 --- src/js/scriptlets/cosmetic-logger.js | 42 ++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/src/js/scriptlets/cosmetic-logger.js b/src/js/scriptlets/cosmetic-logger.js index a096a331c8e72..2d8b11709cd9a 100644 --- a/src/js/scriptlets/cosmetic-logger.js +++ b/src/js/scriptlets/cosmetic-logger.js @@ -30,7 +30,6 @@ if ( typeof vAPI !== 'object' || - vAPI.domFilterer instanceof Object === false || vAPI.domWatcher instanceof Object === false ) { return; @@ -297,6 +296,9 @@ const handlers = { }, onDOMCreated: function() { + if ( vAPI.domFilterer instanceof Object === false ) { + return shutdown(); + } handlers.onFiltersetChanged(vAPI.domFilterer.getAllSelectors()); vAPI.domFilterer.addListener(handlers); attributeObserver.observe(document.body, { @@ -319,17 +321,35 @@ const handlers = { /******************************************************************************/ +const shutdown = function() { + processTimer.clear(); + attributeObserver.disconnect(); + if ( typeof vAPI !== 'object' ) { return; } + if ( vAPI.domFilterer instanceof Object ) { + vAPI.domFilterer.removeListener(handlers); + } + if ( vAPI.domWatcher instanceof Object ) { + vAPI.domWatcher.removeListener(handlers); + } + if ( vAPI.broadcastListener instanceof Object ) { + vAPI.broadcastListener.remove(broadcastListener); + } +}; + +/******************************************************************************/ + +const broadcastListener = msg => { + if ( msg.what === 'loggerDisabled' ) { + shutdown(); + } +}; + +/******************************************************************************/ + vAPI.messaging.extend().then(extended => { - if ( extended !== true ) { return; } - const broadcastListener = msg => { - if ( msg.what === 'loggerDisabled' ) { - processTimer.clear(); - attributeObserver.disconnect(); - vAPI.domFilterer.removeListener(handlers); - vAPI.domWatcher.removeListener(handlers); - vAPI.broadcastListener.remove(broadcastListener); - } - }; + if ( extended !== true ) { + return shutdown(); + } vAPI.broadcastListener.add(broadcastListener); }); From 9a8f1678e566b57857bc61b77428b2065706fa34 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 27 Jul 2020 13:34:35 -0400 Subject: [PATCH 19/38] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 0a9bfe1536bd2..30b75b181c7e7 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.28.5.8 +1.28.5.9 From 188ccb4a04c1464f0993fc5260659c21f553b13b Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 27 Jul 2020 13:40:56 -0400 Subject: [PATCH 20/38] Make Firefox dev build auto-update --- dist/firefox/updates.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index 686abee8f5308..c53ba9e0b6b95 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,10 +3,10 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.28.5.8", + "version": "1.28.5.9", "browser_specific_settings": { "gecko": { "strict_min_version": "55" } }, - "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b8", - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b8/uBlock0_1.28.5b8.firefox.signed.xpi" + "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b9", + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b9/uBlock0_1.28.5b9.firefox.signed.xpi" } ] } From 3632c1821e7bde9caf3d10f38e9b527c7ee3f7dc Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 29 Jul 2020 07:13:08 -0400 Subject: [PATCH 21/38] Tabs opened from about:newtab are not popup candidates Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/1184 --- src/js/tab.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/js/tab.js b/src/js/tab.js index 2e978163d128b..55b400dd5e214 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -513,6 +513,10 @@ housekeep itself. } }; + // https://github.com/uBlockOrigin/uBlock-issues/issues/1184 + // Do not consider a tab opened from `about:newtab` to be a popup + // candidate. + const onTabCreated = async function(createDetails) { const { sourceTabId, sourceFrameId, tabId } = createDetails; const popup = popupCandidates.get(tabId); @@ -533,6 +537,13 @@ housekeep itself. catch (reason) { return; } + if ( + Array.isArray(openerDetails) === false || + openerDetails.length !== 2 || + openerDetails[1].url === 'about:newtab' + ) { + return; + } popupCandidates.set( tabId, new PopupCandidate(createDetails, openerDetails) From aa37166ae713a75d5aff4c3e92c83683d05e114e Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 29 Jul 2020 07:38:49 -0400 Subject: [PATCH 22/38] Code review re. content scripts in about:blank frames Related commit: - https://github.com/gorhill/uBlock/commit/3b72c7cb046e6880149fc2adf669b1f77d822d9a --- src/js/contentscript.js | 36 +++++++++++++--------------- src/js/scriptlets/cosmetic-logger.js | 2 +- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/js/contentscript.js b/src/js/contentscript.js index f9b0950676ead..4964cb47a34b7 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -120,18 +120,17 @@ vAPI.contentScript = true; // https://github.com/uBlockOrigin/uBlock-issues/issues/688#issuecomment-663657508 { - let location = self.location; - if ( location.protocol === 'about:' ) { - try { - let context = self; - do { - context = context.parent; - location = context.location; - } while ( context !== self.top && location.protocol === 'about:' ); - } catch(ex) { + let context = self; + try { + while ( + context !== self.top && + context.location.protocol === 'about:' + ) { + context = context.parent; } + } catch(ex) { } - vAPI.pageLocation = location; + vAPI.effectiveSelf = context; } /******************************************************************************/ @@ -329,11 +328,11 @@ vAPI.SafeAnimationFrame = class { const ignoreTags = new Set([ 'br', 'head', 'link', 'meta', 'script', 'style' ]); const listeners = []; - let domIsReady = false, - domLayoutObserver, - listenerIterator = [], listenerIteratorDirty = false, - removedNodes = false, - safeObserverHandlerTimer; + let domLayoutObserver; + let listenerIterator = []; + let listenerIteratorDirty = false; + let removedNodes = false; + let safeObserverHandlerTimer; const safeObserverHandler = function() { let i = addedNodeLists.length; @@ -393,7 +392,7 @@ vAPI.SafeAnimationFrame = class { }; const startMutationObserver = function() { - if ( domLayoutObserver !== undefined || !domIsReady ) { return; } + if ( domLayoutObserver !== undefined ) { return; } domLayoutObserver = new MutationObserver(observerHandler); domLayoutObserver.observe(document.documentElement, { //attributeFilter: [ 'class', 'id' ], @@ -423,7 +422,7 @@ vAPI.SafeAnimationFrame = class { if ( listeners.indexOf(listener) !== -1 ) { return; } listeners.push(listener); listenerIteratorDirty = true; - if ( domIsReady !== true ) { return; } + if ( domLayoutObserver === undefined ) { return; } try { listener.onDOMCreated(); } catch (ex) { } startMutationObserver(); @@ -451,7 +450,6 @@ vAPI.SafeAnimationFrame = class { }; const start = function() { - domIsReady = true; for ( const listener of getListenerIterator() ) { try { listener.onDOMCreated(); } catch (ex) { } @@ -1795,7 +1793,7 @@ vAPI.injectScriptlet = function(doc, text) { vAPI.bootstrap = function() { vAPI.messaging.send('contentscript', { what: 'retrieveContentScriptParameters', - url: vAPI.pageLocation.href, + url: vAPI.effectiveSelf.location.href, isRootFrame: self === self.top, charset: document.characterSet, }).then(response => { diff --git a/src/js/scriptlets/cosmetic-logger.js b/src/js/scriptlets/cosmetic-logger.js index 2d8b11709cd9a..ca17d53216724 100644 --- a/src/js/scriptlets/cosmetic-logger.js +++ b/src/js/scriptlets/cosmetic-logger.js @@ -210,7 +210,7 @@ const processTimer = new vAPI.SafeAnimationFrame(( ) => { if ( toLog.length === 0 ) { return; } - const location = vAPI.pageLocation || self.location; + const location = vAPI.effectiveSelf.location; vAPI.messaging.send('scriptlets', { what: 'logCosmeticFilteringData', From eddfbf9a188f392253dad56654201271f65166b1 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 29 Jul 2020 09:27:13 -0400 Subject: [PATCH 23/38] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 30b75b181c7e7..03046e8ef8e3c 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.28.5.9 +1.28.5.10 From 2931b4181f59f08b42c03f14086095b9e2b1644a Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Wed, 29 Jul 2020 09:40:48 -0400 Subject: [PATCH 24/38] Make Firefox dev build auto-update --- dist/firefox/updates.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index c53ba9e0b6b95..641f897230ef5 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,10 +3,10 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.28.5.9", + "version": "1.28.5.10", "browser_specific_settings": { "gecko": { "strict_min_version": "55" } }, - "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b9", - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b9/uBlock0_1.28.5b9.firefox.signed.xpi" + "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b10", + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b10/uBlock0_1.28.5b10.firefox.signed.xpi" } ] } From 7dd48a6c8c4d9153b4bc3f56904f3abfb5b23512 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 30 Jul 2020 11:58:49 -0400 Subject: [PATCH 25/38] Allow `:upward()` operator to select `html` element Related feedback: - https://www.reddit.com/r/uBlockOrigin/comments/hvwwj0/element_hiding_by_url_not_by_domain_is_it_possible/fyzykw0/ --- src/js/contentscript.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/js/contentscript.js b/src/js/contentscript.js index 4964cb47a34b7..da8e4c7aac1c6 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -573,8 +573,12 @@ vAPI.injectScriptlet = function(doc, text) { const PSelectorSpathTask = class { constructor(task) { this.spath = task[1]; + this.nth = /^(?:\s*[+~]|:)/.test(this.spath); } - transpose(node, output) { + qsa(node) { + if ( this.nth === false ) { + return node.querySelectorAll(this.spath); + } const parent = node.parentElement; if ( parent === null ) { return; } let pos = 1; @@ -583,9 +587,13 @@ vAPI.injectScriptlet = function(doc, text) { if ( node === null ) { break; } pos += 1; } - const nodes = parent.querySelectorAll( + return parent.querySelectorAll( `:scope > :nth-child(${pos})${this.spath}` ); + } + transpose(node, output) { + const nodes = this.qsa(node); + if ( nodes === undefined ) { return; } for ( const node of nodes ) { output.push(node); } From 90c7e79f4f30ca9ad93be507009a75f009672941 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sat, 1 Aug 2020 10:32:40 -0400 Subject: [PATCH 26/38] Consolidate filter list reverse lookup code into a single file Since it's possible to execute specific code paths according to whether the context is that of a worker or not, it's possible to keep the main/thread code in a single file. Keeping the main/worker code paths into a single file is more convenient for both code maintenance and code review. --- src/js/reverselookup-worker.js | 293 ----------------- src/js/reverselookup.js | 574 ++++++++++++++++++++++++--------- 2 files changed, 419 insertions(+), 448 deletions(-) delete mode 100644 src/js/reverselookup-worker.js diff --git a/src/js/reverselookup-worker.js b/src/js/reverselookup-worker.js deleted file mode 100644 index 9aee1f9a4726c..0000000000000 --- a/src/js/reverselookup-worker.js +++ /dev/null @@ -1,293 +0,0 @@ -/******************************************************************************* - - uBlock Origin - a browser extension to block requests. - Copyright (C) 2015-present Raymond Hill - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see {http://www.gnu.org/licenses/}. - - Home: https://github.com/gorhill/uBlock -*/ - -/* global onmessage, postMessage */ - -'use strict'; - -/******************************************************************************/ - -const reBlockStart = /^#block-start-(\d+)\n/gm; -let listEntries = Object.create(null); - -/******************************************************************************/ - -const extractBlocks = function(content, begId, endId) { - reBlockStart.lastIndex = 0; - const out = []; - let match = reBlockStart.exec(content); - while ( match !== null ) { - const beg = match.index + match[0].length; - const blockId = parseInt(match[1], 10); - if ( blockId >= begId && blockId < endId ) { - const end = content.indexOf('#block-end-' + match[1], beg); - out.push(content.slice(beg, end)); - reBlockStart.lastIndex = end; - } - match = reBlockStart.exec(content); - } - return out.join('\n'); -}; - -/******************************************************************************/ - -// https://github.com/MajkiIT/polish-ads-filter/issues/14768#issuecomment-536006312 -// Avoid reporting badfilter-ed filters. - -const fromNetFilter = function(details) { - const lists = []; - const compiledFilter = details.compiledFilter; - - for ( const assetKey in listEntries ) { - const entry = listEntries[assetKey]; - if ( entry === undefined ) { continue; } - const content = extractBlocks(entry.content, 0, 1); - let pos = 0; - for (;;) { - pos = content.indexOf(compiledFilter, pos); - if ( pos === -1 ) { break; } - // We need an exact match. - // https://github.com/gorhill/uBlock/issues/1392 - // https://github.com/gorhill/uBlock/issues/835 - const notFound = pos !== 0 && content.charCodeAt(pos - 1) !== 0x0A; - pos += compiledFilter.length; - if ( - notFound || - pos !== content.length && content.charCodeAt(pos) !== 0x0A - ) { - continue; - } - lists.push({ - assetKey: assetKey, - title: entry.title, - supportURL: entry.supportURL - }); - break; - } - } - - const response = {}; - response[details.rawFilter] = lists; - - postMessage({ - id: details.id, - response: response - }); -}; - -/******************************************************************************/ - -// Looking up filter lists from a cosmetic filter is a bit more complicated -// than with network filters: -// -// The filter is its raw representation, not its compiled version. This is -// because the cosmetic filtering engine can't translate a live cosmetic -// filter into its compiled version. Reason is I do not want to burden -// cosmetic filtering with the resource overhead of being able to re-compile -// live cosmetic filters. I want the cosmetic filtering code to be left -// completely unaffected by reverse lookup requirements. -// -// Mainly, given a CSS selector and a hostname as context, we will derive -// various versions of compiled filters and see if there are matches. This way -// the whole CPU cost is incurred by the reverse lookup code -- in a worker -// thread, and the cosmetic filtering engine incurs no cost at all. -// -// For this though, the reverse lookup code here needs some knowledge of -// the inners of the cosmetic filtering engine. -// FilterContainer.fromCompiledContent() is our reference code to create -// the various compiled versions. - -const fromCosmeticFilter = function(details) { - const match = /^#@?#\^?/.exec(details.rawFilter); - const prefix = match[0]; - const exception = prefix.charAt(1) === '@'; - const selector = details.rawFilter.slice(prefix.length); - const isHtmlFilter = prefix.endsWith('^'); - const hostname = details.hostname; - - // The longer the needle, the lower the number of false positives. - // https://github.com/uBlockOrigin/uBlock-issues/issues/1139 - // Mind that there is no guarantee a selector has `\w` characters. - const needle = selector.match(/\w+|\*/g).reduce(function(a, b) { - return a.length > b.length ? a : b; - }); - - const regexFromLabels = (prefix, hn, suffix) => - new RegExp( - prefix + - hn.split('.').reduce((acc, item) => `(${acc}\\.)?${item}`) + - suffix - ); - - // https://github.com/uBlockOrigin/uBlock-issues/issues/803 - // Support looking up selectors of the form `*##...` - const reHostname = regexFromLabels('^', hostname, '$'); - let reEntity; - { - const domain = details.domain; - const pos = domain.indexOf('.'); - if ( pos !== -1 ) { - reEntity = regexFromLabels( - '^(', - hostname.slice(0, pos + hostname.length - domain.length), - '\\.)?\\*$' - ); - } - } - - const hostnameMatches = hn => { - return hn === '' || - reHostname.test(hn) || - reEntity !== undefined && reEntity.test(hn); - }; - - const response = Object.create(null); - - for ( const assetKey in listEntries ) { - const entry = listEntries[assetKey]; - if ( entry === undefined ) { continue; } - let content = extractBlocks(entry.content, 1000, 2000), - isProcedural, - found; - let pos = 0; - while ( (pos = content.indexOf(needle, pos)) !== -1 ) { - let beg = content.lastIndexOf('\n', pos); - if ( beg === -1 ) { beg = 0; } - let end = content.indexOf('\n', pos); - if ( end === -1 ) { end = content.length; } - pos = end; - const fargs = JSON.parse(content.slice(beg, end)); - const filterType = fargs[0]; - - // https://github.com/gorhill/uBlock/issues/2763 - if ( filterType >= 0 && filterType <= 5 && details.ignoreGeneric ) { - continue; - } - - // Do not confuse cosmetic filters with HTML ones. - if ( (filterType === 64) !== isHtmlFilter ) { continue; } - - switch ( filterType ) { - // Lowly generic cosmetic filters - case 0: // simple id-based - if ( exception ) { break; } - if ( fargs[1] !== selector.slice(1) ) { break; } - if ( selector.charAt(0) !== '#' ) { break; } - found = prefix + selector; - break; - case 2: // simple class-based - if ( exception ) { break; } - if ( fargs[1] !== selector.slice(1) ) { break; } - if ( selector.charAt(0) !== '.' ) { break; } - found = prefix + selector; - break; - case 1: // complex id-based - case 3: // complex class-based - if ( exception ) { break; } - if ( fargs[2] !== selector ) { break; } - found = prefix + selector; - break; - // Highly generic cosmetic filters - case 4: // simple highly generic - case 5: // complex highly generic - if ( exception ) { break; } - if ( fargs[1] !== selector ) { break; } - found = prefix + selector; - break; - // Specific cosmetic filtering - // Generic exception - case 8: - // HTML filtering - case 64: - if ( exception !== ((fargs[2] & 0b001) !== 0) ) { break; } - isProcedural = (fargs[2] & 0b010) !== 0; - if ( - isProcedural === false && fargs[3] !== selector || - isProcedural && JSON.parse(fargs[3]).raw !== selector - ) { - break; - } - if ( hostnameMatches(fargs[1]) === false ) { break; } - // https://www.reddit.com/r/uBlockOrigin/comments/d6vxzj/ - // Ignore match if specific cosmetic filters are disabled - if ( - filterType === 8 && - exception === false && - details.ignoreSpecific - ) { - break; - } - found = fargs[1] + prefix + selector; - break; - // Scriptlet injection - case 32: - if ( exception !== ((fargs[2] & 0b001) !== 0) ) { break; } - if ( fargs[3] !== selector ) { break; } - if ( hostnameMatches(fargs[1]) ) { - found = fargs[1] + prefix + selector; - } - break; - } - if ( found !== undefined ) { - if ( response[found] === undefined ) { - response[found] = []; - } - response[found].push({ - assetKey: assetKey, - title: entry.title, - supportURL: entry.supportURL - }); - break; - } - } - } - - postMessage({ - id: details.id, - response: response - }); -}; - -/******************************************************************************/ - -onmessage = function(e) { // jshint ignore:line - const msg = e.data; - - switch ( msg.what ) { - case 'resetLists': - listEntries = Object.create(null); - break; - - case 'setList': - listEntries[msg.details.assetKey] = msg.details; - break; - - case 'fromNetFilter': - fromNetFilter(msg); - break; - - case 'fromCosmeticFilter': - fromCosmeticFilter(msg); - break; - } -}; - -/******************************************************************************/ diff --git a/src/js/reverselookup.js b/src/js/reverselookup.js index 8be3519677601..7d9af5a9a8da4 100644 --- a/src/js/reverselookup.js +++ b/src/js/reverselookup.js @@ -23,198 +23,462 @@ /******************************************************************************/ -µBlock.staticFilteringReverseLookup = (( ) => { +(( ) => { +// >>>>> start of local scope /******************************************************************************/ -const workerTTL = 5 * 60 * 1000; -const pendingResponses = new Map(); +// Worker context + +if ( + self.WorkerGlobalScope instanceof Object && + self instanceof self.WorkerGlobalScope +) { + const reBlockStart = /^#block-start-(\d+)\n/gm; + let listEntries = Object.create(null); + + const extractBlocks = function(content, begId, endId) { + reBlockStart.lastIndex = 0; + const out = []; + let match = reBlockStart.exec(content); + while ( match !== null ) { + const beg = match.index + match[0].length; + const blockId = parseInt(match[1], 10); + if ( blockId >= begId && blockId < endId ) { + const end = content.indexOf('#block-end-' + match[1], beg); + out.push(content.slice(beg, end)); + reBlockStart.lastIndex = end; + } + match = reBlockStart.exec(content); + } + return out.join('\n'); + }; -let worker = null; -let workerTTLTimer; -let needLists = true; -let messageId = 1; + // https://github.com/MajkiIT/polish-ads-filter/issues/14768#issuecomment-536006312 + // Avoid reporting badfilter-ed filters. + + const fromNetFilter = function(details) { + const lists = []; + const compiledFilter = details.compiledFilter; + + for ( const assetKey in listEntries ) { + const entry = listEntries[assetKey]; + if ( entry === undefined ) { continue; } + const content = extractBlocks(entry.content, 0, 1); + let pos = 0; + for (;;) { + pos = content.indexOf(compiledFilter, pos); + if ( pos === -1 ) { break; } + // We need an exact match. + // https://github.com/gorhill/uBlock/issues/1392 + // https://github.com/gorhill/uBlock/issues/835 + const notFound = pos !== 0 && + content.charCodeAt(pos - 1) !== 0x0A; + pos += compiledFilter.length; + if ( + notFound || + pos !== content.length && content.charCodeAt(pos) !== 0x0A + ) { + continue; + } + lists.push({ + assetKey: assetKey, + title: entry.title, + supportURL: entry.supportURL + }); + break; + } + } -/******************************************************************************/ + const response = {}; + response[details.rawFilter] = lists; -const onWorkerMessage = function(e) { - const msg = e.data; - const resolver = pendingResponses.get(msg.id); - pendingResponses.delete(msg.id); - resolver(msg.response); -}; + self.postMessage({ id: details.id, response }); + }; -/******************************************************************************/ + // Looking up filter lists from a cosmetic filter is a bit more complicated + // than with network filters: + // + // The filter is its raw representation, not its compiled version. This is + // because the cosmetic filtering engine can't translate a live cosmetic + // filter into its compiled version. Reason is I do not want to burden + // cosmetic filtering with the resource overhead of being able to recompile + // live cosmetic filters. I want the cosmetic filtering code to be left + // completely unaffected by reverse lookup requirements. + // + // Mainly, given a CSS selector and a hostname as context, we will derive + // various versions of compiled filters and see if there are matches. This + // way the whole CPU cost is incurred by the reverse lookup code -- in a + // worker thread, and the cosmetic filtering engine incurs no cost at all. + // + // For this though, the reverse lookup code here needs some knowledge of + // the inners of the cosmetic filtering engine. + // FilterContainer.fromCompiledContent() is our reference code to create + // the various compiled versions. + + const fromCosmeticFilter = function(details) { + const match = /^#@?#\^?/.exec(details.rawFilter); + const prefix = match[0]; + const exception = prefix.charAt(1) === '@'; + const selector = details.rawFilter.slice(prefix.length); + const isHtmlFilter = prefix.endsWith('^'); + const hostname = details.hostname; + + // The longer the needle, the lower the number of false positives. + // https://github.com/uBlockOrigin/uBlock-issues/issues/1139 + // Mind that there is no guarantee a selector has `\w` characters. + const needle = selector.match(/\w+|\*/g).reduce(function(a, b) { + return a.length > b.length ? a : b; + }); -const stopWorker = function() { - if ( workerTTLTimer !== undefined ) { - clearTimeout(workerTTLTimer); - workerTTLTimer = undefined; - } - if ( worker === null ) { return; } - worker.terminate(); - worker = null; - needLists = true; - for ( const resolver of pendingResponses.values() ) { - resolver(); - } - pendingResponses.clear(); -}; + const regexFromLabels = (prefix, hn, suffix) => + new RegExp( + prefix + + hn.split('.').reduce((acc, item) => `(${acc}\\.)?${item}`) + + suffix + ); + + // https://github.com/uBlockOrigin/uBlock-issues/issues/803 + // Support looking up selectors of the form `*##...` + const reHostname = regexFromLabels('^', hostname, '$'); + let reEntity; + { + const domain = details.domain; + const pos = domain.indexOf('.'); + if ( pos !== -1 ) { + reEntity = regexFromLabels( + '^(', + hostname.slice(0, pos + hostname.length - domain.length), + '\\.)?\\*$' + ); + } + } + + const hostnameMatches = hn => { + return hn === '' || + reHostname.test(hn) || + reEntity !== undefined && reEntity.test(hn); + }; + + const response = Object.create(null); + + for ( const assetKey in listEntries ) { + const entry = listEntries[assetKey]; + if ( entry === undefined ) { continue; } + let content = extractBlocks(entry.content, 1000, 2000), + isProcedural, + found; + let pos = 0; + while ( (pos = content.indexOf(needle, pos)) !== -1 ) { + let beg = content.lastIndexOf('\n', pos); + if ( beg === -1 ) { beg = 0; } + let end = content.indexOf('\n', pos); + if ( end === -1 ) { end = content.length; } + pos = end; + const fargs = JSON.parse(content.slice(beg, end)); + const filterType = fargs[0]; + + // https://github.com/gorhill/uBlock/issues/2763 + if ( + filterType >= 0 && + filterType <= 5 && + details.ignoreGeneric + ) { + continue; + } + + // Do not confuse cosmetic filters with HTML ones. + if ( (filterType === 64) !== isHtmlFilter ) { continue; } + + switch ( filterType ) { + // Lowly generic cosmetic filters + case 0: // simple id-based + if ( exception ) { break; } + if ( fargs[1] !== selector.slice(1) ) { break; } + if ( selector.charAt(0) !== '#' ) { break; } + found = prefix + selector; + break; + case 2: // simple class-based + if ( exception ) { break; } + if ( fargs[1] !== selector.slice(1) ) { break; } + if ( selector.charAt(0) !== '.' ) { break; } + found = prefix + selector; + break; + case 1: // complex id-based + case 3: // complex class-based + if ( exception ) { break; } + if ( fargs[2] !== selector ) { break; } + found = prefix + selector; + break; + // Highly generic cosmetic filters + case 4: // simple highly generic + case 5: // complex highly generic + if ( exception ) { break; } + if ( fargs[1] !== selector ) { break; } + found = prefix + selector; + break; + // Specific cosmetic filtering + // Generic exception + case 8: + // HTML filtering + case 64: + if ( exception !== ((fargs[2] & 0b001) !== 0) ) { break; } + isProcedural = (fargs[2] & 0b010) !== 0; + if ( + isProcedural === false && fargs[3] !== selector || + isProcedural && JSON.parse(fargs[3]).raw !== selector + ) { + break; + } + if ( hostnameMatches(fargs[1]) === false ) { break; } + // https://www.reddit.com/r/uBlockOrigin/comments/d6vxzj/ + // Ignore match if specific cosmetic filters are disabled + if ( + filterType === 8 && + exception === false && + details.ignoreSpecific + ) { + break; + } + found = fargs[1] + prefix + selector; + break; + // Scriptlet injection + case 32: + if ( exception !== ((fargs[2] & 0b001) !== 0) ) { break; } + if ( fargs[3] !== selector ) { break; } + if ( hostnameMatches(fargs[1]) ) { + found = fargs[1] + prefix + selector; + } + break; + } + if ( found !== undefined ) { + if ( response[found] === undefined ) { + response[found] = []; + } + response[found].push({ + assetKey: assetKey, + title: entry.title, + supportURL: entry.supportURL + }); + break; + } + } + } + + self.postMessage({ id: details.id, response }); + }; + + self.onmessage = function(e) { // jshint ignore:line + const msg = e.data; + + switch ( msg.what ) { + case 'resetLists': + listEntries = Object.create(null); + break; + + case 'setList': + listEntries[msg.details.assetKey] = msg.details; + break; + + case 'fromNetFilter': + fromNetFilter(msg); + break; + + case 'fromCosmeticFilter': + fromCosmeticFilter(msg); + break; + } + }; + + return; +} /******************************************************************************/ -const initWorker = function() { - if ( worker === null ) { - worker = new Worker('js/reverselookup-worker.js'); - worker.onmessage = onWorkerMessage; - } +// Main context - // The worker will be shutdown after n minutes without being used. - if ( workerTTLTimer !== undefined ) { - clearTimeout(workerTTLTimer); - } - workerTTLTimer = vAPI.setTimeout(stopWorker, workerTTL); +{ + if ( typeof µBlock !== 'object' ) { return; } - if ( needLists === false ) { - return Promise.resolve(); - } - needLists = false; + const workerTTL = 5 * 60 * 1000; + const pendingResponses = new Map(); - const entries = new Map(); + let worker = null; + let workerTTLTimer; + let needLists = true; + let messageId = 1; - const onListLoaded = function(details) { - const entry = entries.get(details.assetKey); + const onWorkerMessage = function(e) { + const msg = e.data; + const resolver = pendingResponses.get(msg.id); + pendingResponses.delete(msg.id); + resolver(msg.response); + }; - // https://github.com/gorhill/uBlock/issues/536 - // Use assetKey when there is no filter list title. + const stopWorker = function() { + if ( workerTTLTimer !== undefined ) { + clearTimeout(workerTTLTimer); + workerTTLTimer = undefined; + } + if ( worker === null ) { return; } + worker.terminate(); + worker = null; + needLists = true; + for ( const resolver of pendingResponses.values() ) { + resolver(); + } + pendingResponses.clear(); + }; - worker.postMessage({ - what: 'setList', - details: { - assetKey: details.assetKey, - title: entry.title || details.assetKey, - supportURL: entry.supportURL, - content: details.content + const initWorker = function() { + if ( worker === null ) { + worker = new Worker('js/reverselookup.js'); + worker.onmessage = onWorkerMessage; + } + + // The worker will be shutdown after n minutes without being used. + if ( workerTTLTimer !== undefined ) { + clearTimeout(workerTTLTimer); + } + workerTTLTimer = vAPI.setTimeout(stopWorker, workerTTL); + + if ( needLists === false ) { + return Promise.resolve(); + } + needLists = false; + + const entries = new Map(); + + const onListLoaded = function(details) { + const entry = entries.get(details.assetKey); + + // https://github.com/gorhill/uBlock/issues/536 + // Use assetKey when there is no filter list title. + + worker.postMessage({ + what: 'setList', + details: { + assetKey: details.assetKey, + title: entry.title || details.assetKey, + supportURL: entry.supportURL, + content: details.content + } + }); + }; + + const µb = µBlock; + for ( const listKey in µb.availableFilterLists ) { + if ( µb.availableFilterLists.hasOwnProperty(listKey) === false ) { + continue; } - }); - }; + const entry = µb.availableFilterLists[listKey]; + if ( entry.off === true ) { continue; } + entries.set(listKey, { + title: listKey !== µb.userFiltersPath ? + entry.title : + vAPI.i18n('1pPageName'), + supportURL: entry.supportURL || '' + }); + } + if ( entries.size === 0 ) { + return Promise.resolve(); + } - const µb = µBlock; - for ( const listKey in µb.availableFilterLists ) { - if ( µb.availableFilterLists.hasOwnProperty(listKey) === false ) { - continue; + const promises = []; + for ( const listKey of entries.keys() ) { + promises.push( + µb.getCompiledFilterList(listKey).then(details => { + onListLoaded(details); + }) + ); } - const entry = µb.availableFilterLists[listKey]; - if ( entry.off === true ) { continue; } - entries.set(listKey, { - title: listKey !== µb.userFiltersPath ? - entry.title : - vAPI.i18n('1pPageName'), - supportURL: entry.supportURL || '' - }); - } - if ( entries.size === 0 ) { - return Promise.resolve(); - } - - const promises = []; - for ( const listKey of entries.keys() ) { - promises.push( - µb.getCompiledFilterList(listKey).then(details => { - onListLoaded(details); - }) - ); - } - return Promise.all(promises); -}; + return Promise.all(promises); + }; -/******************************************************************************/ + const fromNetFilter = async function(rawFilter) { + if ( typeof rawFilter !== 'string' || rawFilter === '' ) { return; } + + const µb = µBlock; + const writer = new µb.CompiledLineIO.Writer(); + const parser = new vAPI.StaticFilteringParser(); + parser.setMaxTokenLength(µb.urlTokenizer.MAX_TOKEN_LENGTH); + parser.analyze(rawFilter); -const fromNetFilter = async function(rawFilter) { - if ( typeof rawFilter !== 'string' || rawFilter === '' ) { return; } + if ( µb.staticNetFilteringEngine.compile(parser, writer) === false ) { + return; + } - const µb = µBlock; - const writer = new µb.CompiledLineIO.Writer(); - const parser = new vAPI.StaticFilteringParser(); - parser.setMaxTokenLength(µb.urlTokenizer.MAX_TOKEN_LENGTH); - parser.analyze(rawFilter); + await initWorker(); - if ( µb.staticNetFilteringEngine.compile(parser, writer) === false ) { - return; - } + const id = messageId++; + worker.postMessage({ + what: 'fromNetFilter', + id: id, + compiledFilter: writer.last(), + rawFilter: rawFilter + }); - await initWorker(); + return new Promise(resolve => { + pendingResponses.set(id, resolve); + }); + }; - const id = messageId++; - worker.postMessage({ - what: 'fromNetFilter', - id: id, - compiledFilter: writer.last(), - rawFilter: rawFilter - }); + const fromCosmeticFilter = async function(details) { + if ( + typeof details.rawFilter !== 'string' || + details.rawFilter === '' + ) { + return; + } - return new Promise(resolve => { - pendingResponses.set(id, resolve); - }); -}; + await initWorker(); -/******************************************************************************/ + const id = messageId++; + const hostname = µBlock.URI.hostnameFromURI(details.url); -const fromCosmeticFilter = async function(details) { - if ( typeof details.rawFilter !== 'string' || details.rawFilter === '' ) { - return; - } - - await initWorker(); - - const id = messageId++; - const hostname = µBlock.URI.hostnameFromURI(details.url); - - worker.postMessage({ - what: 'fromCosmeticFilter', - id: id, - domain: µBlock.URI.domainFromHostname(hostname), - hostname: hostname, - ignoreGeneric: - µBlock.staticNetFilteringEngine.matchStringReverse( - 'generichide', - details.url - ) === 2, - ignoreSpecific: - µBlock.staticNetFilteringEngine.matchStringReverse( - 'specifichide', - details.url - ) === 2, - rawFilter: details.rawFilter - }); - - return new Promise(resolve => { - pendingResponses.set(id, resolve); - }); - -}; + worker.postMessage({ + what: 'fromCosmeticFilter', + id: id, + domain: µBlock.URI.domainFromHostname(hostname), + hostname: hostname, + ignoreGeneric: + µBlock.staticNetFilteringEngine.matchStringReverse( + 'generichide', + details.url + ) === 2, + ignoreSpecific: + µBlock.staticNetFilteringEngine.matchStringReverse( + 'specifichide', + details.url + ) === 2, + rawFilter: details.rawFilter + }); -/******************************************************************************/ + return new Promise(resolve => { + pendingResponses.set(id, resolve); + }); -// This tells the worker that filter lists may have changed. + }; -const resetLists = function() { - needLists = true; - if ( worker === null ) { return; } - worker.postMessage({ what: 'resetLists' }); -}; + // This tells the worker that filter lists may have changed. -/******************************************************************************/ + const resetLists = function() { + needLists = true; + if ( worker === null ) { return; } + worker.postMessage({ what: 'resetLists' }); + }; -return { - fromNetFilter, - fromCosmeticFilter, - resetLists, - shutdown: stopWorker -}; + µBlock.staticFilteringReverseLookup = { + fromNetFilter, + fromCosmeticFilter, + resetLists, + shutdown: stopWorker + }; +} /******************************************************************************/ +// <<<<< end of local scope })(); /******************************************************************************/ From 23332400f5f16de1a54265dd7bb110ea7e1da57c Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 2 Aug 2020 12:18:01 -0400 Subject: [PATCH 27/38] Improve annotations for search operations in CodeMirror editor Before this commit, CodeMirror's add-on for search occurrences was limited to find at most 1000 first occurrences, because of performance considerations. This commit removes this low limit by having the search occurrences done in a dedicated worker. The limit is now time-based, and highly unlikely to ever be hit under normal condition. With this change, all search occurrences are gathered, and as a result: - All occurrences are reported in the scrollbar instead of just the 1,000 first - The total count of all occurrences is now reported, instead of capping at "1000+". - The current occurrence rank at the cursor or selection position is now reported -- this was not possible to report this before. The number of occurrences is line-based, it's not useful to report finer-grained occurences in uBO. --- src/1p-filters.html | 2 +- src/asset-viewer.html | 2 +- src/css/codemirror.css | 6 +- src/js/codemirror/search-thread.js | 196 ++++++++++ src/js/codemirror/search.js | 362 ++++++++++++------ src/js/reverselookup.js | 1 - .../addon/search/matchesonscrollbar.js | 97 ----- src/whitelist.html | 2 +- 8 files changed, 440 insertions(+), 228 deletions(-) create mode 100644 src/js/codemirror/search-thread.js delete mode 100644 src/lib/codemirror/addon/search/matchesonscrollbar.js diff --git a/src/1p-filters.html b/src/1p-filters.html index b5b0d25d922bf..e99dd8a05bacd 100644 --- a/src/1p-filters.html +++ b/src/1p-filters.html @@ -47,11 +47,11 @@ - + diff --git a/src/asset-viewer.html b/src/asset-viewer.html index 0aca82e511b2c..c3a2badde5bce 100644 --- a/src/asset-viewer.html +++ b/src/asset-viewer.html @@ -35,11 +35,11 @@ - + diff --git a/src/css/codemirror.css b/src/css/codemirror.css index 411804f7fb057..e17b5caa3f6bf 100644 --- a/src/css/codemirror.css +++ b/src/css/codemirror.css @@ -83,8 +83,8 @@ div.CodeMirror span.CodeMirror-matchingbracket { border: 1px solid gray; border-radius: 3px; display: inline-flex; - max-width: 50vw; - width: 16em; + min-width: 14em; + width: 30vw; } .cm-search-widget-input > input { border: 0; @@ -93,12 +93,10 @@ div.CodeMirror span.CodeMirror-matchingbracket { } .cm-search-widget-input > .cm-search-widget-count { align-items: center; - color: #888; display: none; flex-grow: 0; font-size: 80%; padding: 0 0.4em; - pointer-events: none; } .cm-search-widget[data-query] .cm-search-widget-count { display: inline-flex; diff --git a/src/js/codemirror/search-thread.js b/src/js/codemirror/search-thread.js new file mode 100644 index 0000000000000..29d4465d6b9dd --- /dev/null +++ b/src/js/codemirror/search-thread.js @@ -0,0 +1,196 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2020-present Raymond Hill + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +'use strict'; + +/******************************************************************************/ + +(( ) => { +// >>>>> start of local scope + +/******************************************************************************/ + +// Worker context + +if ( + self.WorkerGlobalScope instanceof Object && + self instanceof self.WorkerGlobalScope +) { + let content = ''; + + const doSearch = function(details) { + const reEOLs = /\n\r|\r\n|\n|\r/g; + const t1 = Date.now() + 750; + + let reSearch; + try { + reSearch = new RegExp(details.pattern, details.flags); + } catch(ex) { + return; + } + + const response = []; + const maxOffset = content.length; + let iLine = 0; + let iOffset = 0; + let size = 0; + while ( iOffset < maxOffset ) { + // Find next match + const match = reSearch.exec(content); + if ( match === null ) { break; } + // Find number of line breaks between last and current match. + reEOLs.lastIndex = 0; + const eols = content.slice(iOffset, match.index).match(reEOLs); + if ( Array.isArray(eols) ) { + iLine += eols.length; + } + // Store line + response.push(iLine); + size += 1; + // Find next line break. + reEOLs.lastIndex = reSearch.lastIndex; + const eol = reEOLs.exec(content); + iOffset = eol !== null + ? reEOLs.lastIndex + : content.length; + reSearch.lastIndex = iOffset; + iLine += 1; + // Quit if this takes too long + if ( (size & 0x3FF) === 0 && Date.now() >= t1 ) { break; } + } + + return response; + }; + + self.onmessage = function(e) { + const msg = e.data; + + switch ( msg.what ) { + case 'setHaystack': + content = msg.content; + break; + + case 'doSearch': + const response = doSearch(msg); + self.postMessage({ id: msg.id, response }); + break; + } + }; + + return; +} + +/******************************************************************************/ + +// Main context + +{ + const workerTTL = 5 * 60 * 1000; + const pendingResponses = new Map(); + + let worker; + let workerTTLTimer; + let messageId = 1; + + const onWorkerMessage = function(e) { + const msg = e.data; + const resolver = pendingResponses.get(msg.id); + if ( resolver === undefined ) { return; } + pendingResponses.delete(msg.id); + resolver(msg.response); + }; + + const cancelPendingTasks = function() { + for ( const resolver of pendingResponses.values() ) { + resolver(); + } + pendingResponses.clear(); + }; + + const destroy = function() { + shutdown(); + self.searchThread = undefined; + }; + + const shutdown = function() { + if ( workerTTLTimer !== undefined ) { + clearTimeout(workerTTLTimer); + workerTTLTimer = undefined; + } + if ( worker === undefined ) { return; } + worker.terminate(); + worker.onmessage = undefined; + worker = undefined; + cancelPendingTasks(); + }; + + const init = function() { + if ( self.searchThread instanceof Object === false ) { return; } + if ( worker === undefined ) { + worker = new Worker('js/codemirror/search-thread.js'); + worker.onmessage = onWorkerMessage; + } + if ( workerTTLTimer !== undefined ) { + clearTimeout(workerTTLTimer); + } + workerTTLTimer = vAPI.setTimeout(shutdown, workerTTL); + self.addEventListener('beforeunload', ( ) => { + destroy(); + }, { once: true }); + }; + + const setHaystack = function(content) { + init(); + worker.postMessage({ what: 'setHaystack', content }); + }; + + const search = function(query, overwrite = true) { + init(); + if ( worker instanceof Object === false ) { + return Promise.resolve(); + } + if ( overwrite ) { + cancelPendingTasks(); + } + const id = messageId++; + worker.postMessage({ + what: 'doSearch', + id, + pattern: query.source, + flags: query.flags, + isRE: query instanceof RegExp + }); + return new Promise(resolve => { + pendingResponses.set(id, resolve); + }); + }; + + self.searchThread = { setHaystack, search, shutdown }; +} + +/******************************************************************************/ + +// <<<<< end of local scope +})(); + +/******************************************************************************/ + +void 0; diff --git a/src/js/codemirror/search.js b/src/js/codemirror/search.js index d3420f7bb8861..cc91b742d21f1 100644 --- a/src/js/codemirror/search.js +++ b/src/js/codemirror/search.js @@ -3,8 +3,16 @@ // I added/removed and modified code in order to get a closer match to a // browser's built-in find-in-page feature which are just enough for // uBlock Origin. - - +// +// This file was originally wholly imported from: +// https://github.com/codemirror/CodeMirror/blob/3e1bb5fff682f8f6cbfaef0e56c61d62403d4798/addon/search/search.js +// +// And has been modified over time to better suit uBO's usage and coding style: +// https://github.com/gorhill/uBlock/commits/master/src/js/codemirror/search.js +// +// The original copyright notice is reproduced below: + +// ===== // CodeMirror, copyright (c) by Marijn Haverbeke and others // Distributed under an MIT license: http://codemirror.net/LICENSE @@ -15,44 +23,39 @@ // Ctrl-G (or whatever is bound to findNext) press. You prevent a // replace by making sure the match is no longer selected when hitting // Ctrl-G. - -/* globals define, require, CodeMirror */ +// ===== 'use strict'; -(function(mod) { - if (typeof exports === "object" && typeof module === "object") // CommonJS - mod(require("../../lib/codemirror"), require("./searchcursor"), require("../dialog/dialog")); - else if (typeof define === "function" && define.amd) // AMD - define(["../../lib/codemirror", "./searchcursor", "../dialog/dialog"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - - function searchOverlay(query, caseInsensitive) { - if (typeof query === "string") - query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), caseInsensitive ? "gi" : "g"); - else if (!query.global) - query = new RegExp(query.source, query.ignoreCase ? "gi" : "g"); +(function(CodeMirror) { + + const searchOverlay = function(query, caseInsensitive) { + if ( typeof query === 'string' ) + query = new RegExp( + query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), + caseInsensitive ? 'gi' : 'g' + ); + else if ( !query.global ) + query = new RegExp(query.source, query.ignoreCase ? 'gi' : 'g'); return { token: function(stream) { query.lastIndex = stream.pos; - var match = query.exec(stream.string); - if (match && match.index === stream.pos) { + const match = query.exec(stream.string); + if ( match && match.index === stream.pos ) { stream.pos += match[0].length || 1; - return "searching"; - } else if (match) { + return 'searching'; + } else if ( match ) { stream.pos = match.index; } else { stream.skipToEnd(); } } }; - } + }; - function searchWidgetKeydownHandler(cm, ev) { - var keyName = CodeMirror.keyName(ev); + const searchWidgetKeydownHandler = function(cm, ev) { + const keyName = CodeMirror.keyName(ev); if ( !keyName ) { return; } CodeMirror.lookupKey( keyName, @@ -64,9 +67,9 @@ } } ); - } + }; - function searchWidgetInputHandler(cm) { + const searchWidgetInputHandler = function(cm) { let state = getSearchState(cm); if ( queryTextFromSearchWidget(cm) === state.queryText ) { return; } if ( state.queryTimer !== null ) { @@ -79,10 +82,10 @@ }, 350 ); - } + }; - function searchWidgetClickHandler(cm, ev) { - var tcl = ev.target.classList; + const searchWidgetClickHandler = function(cm, ev) { + const tcl = ev.target.classList; if ( tcl.contains('cm-search-widget-up') ) { findNext(cm, -1); } else if ( tcl.contains('cm-search-widget-down') ) { @@ -93,27 +96,25 @@ } else { ev.stopImmediatePropagation(); } - } + }; - function queryTextFromSearchWidget(cm) { + const queryTextFromSearchWidget = function(cm) { return getSearchState(cm).widget.querySelector('input[type="search"]').value; - } + }; - function queryTextToSearchWidget(cm, q) { - var input = getSearchState(cm).widget.querySelector('input[type="search"]'); + const queryTextToSearchWidget = function(cm, q) { + const input = getSearchState(cm).widget.querySelector('input[type="search"]'); if ( typeof q === 'string' && q !== input.value ) { input.value = q; } input.setSelectionRange(0, input.value.length); input.focus(); - } + }; - function SearchState(cm) { + const SearchState = function(cm) { this.query = null; - this.overlay = null; this.panel = null; - const widgetParent = - document.querySelector('.cm-search-widget-template').cloneNode(true); + const widgetParent = document.querySelector('.cm-search-widget-template').cloneNode(true); this.widget = widgetParent.children[0]; this.widget.addEventListener('keydown', searchWidgetKeydownHandler.bind(null, cm)); this.widget.addEventListener('input', searchWidgetInputHandler.bind(null, cm)); @@ -123,16 +124,29 @@ } this.queryText = ''; this.queryTimer = null; - } + this.dirty = true; + this.lines = []; + cm.on('changes', (cm, changes) => { + for ( const change of changes ) { + if ( change.text.length !== 0 || change.removed !== 0 ) { + this.dirty = true; + break; + } + } + }); + cm.on('cursorActivity', cm => { + updateRank(cm); + }); + }; // We want the search widget to behave as if the focus was on the // CodeMirror editor. const reSearchCommands = /^(?:find|findNext|findPrev|newlineAndIndent)$/; - function widgetCommandHandler(cm, command) { + const widgetCommandHandler = function(cm, command) { if ( reSearchCommands.test(command) === false ) { return false; } - var queryText = queryTextFromSearchWidget(cm); + const queryText = queryTextFromSearchWidget(cm); if ( command === 'find' ) { queryTextToSearchWidget(cm); return true; @@ -141,101 +155,202 @@ findNext(cm, command === 'findPrev' ? -1 : 1); } return true; - } + }; - function getSearchState(cm) { + const getSearchState = function(cm) { return cm.state.search || (cm.state.search = new SearchState(cm)); - } + }; - function queryCaseInsensitive(query) { - return typeof query === "string" && query === query.toLowerCase(); - } + const queryCaseInsensitive = function(query) { + return typeof query === 'string' && query === query.toLowerCase(); + }; - function getSearchCursor(cm, query, pos) { - // Heuristic: if the query string is all lowercase, do a case insensitive search. + // Heuristic: if the query string is all lowercase, do a case insensitive search. + const getSearchCursor = function(cm, query, pos) { return cm.getSearchCursor( query, pos, { caseFold: queryCaseInsensitive(query), multiline: false } ); - } + }; // https://github.com/uBlockOrigin/uBlock-issues/issues/658 // Modified to backslash-escape ONLY widely-used control characters. - function parseString(string) { - return string.replace(/\\[nrt\\]/g, function(match) { - if (match === "\\n") return "\n"; - if (match === "\\r") return "\r"; - if (match === '\\t') return '\t'; - if (match === '\\\\') return '\\'; + const parseString = function(string) { + return string.replace(/\\[nrt\\]/g, match => { + if ( match === '\\n' ) { return '\n'; } + if ( match === '\\r' ) { return '\r'; } + if ( match === '\\t' ) { return '\t'; } + if ( match === '\\\\' ) { return '\\'; } return match; }); - } - - // FIX: use all potential regex flags as is, and if this throws, treat - // the query string as plain text. - function parseQuery(query) { - let isRE = query.match(/^\/(.*)\/([a-z]*)$/); - if ( isRE ) { + }; + + const reEscape = /[.*+\-?^${}()|[\]\\]/g; + + // Must always return a RegExp object. + // + // Assume case-sensitivity if there is at least one uppercase in plain + // query text. + const parseQuery = function(query) { + let flags = 'i'; + let reParsed = query.match(/^\/(.+)\/([iu]*)$/); + if ( reParsed !== null ) { try { - query = new RegExp(isRE[1], isRE[2]); + const re = new RegExp(reParsed[1], reParsed[2]); + query = re.source; + flags = re.flags; } catch (e) { - isRE = false; + reParsed = null; } } - if ( isRE === false ) { - query = parseString(query); + if ( reParsed === null ) { + if ( /[A-Z]/.test(query) ) { flags = ''; } + query = parseString(query).replace(reEscape, '\\$&'); } if ( typeof query === 'string' ? query === '' : query.test('') ) { - query = /x^/; + query = 'x^'; } - return query; - } + return new RegExp(query, 'gm' + flags); + }; + + let intlNumberFormat; + + const formatNumber = function(n) { + if ( intlNumberFormat === undefined ) { + intlNumberFormat = null; + if ( Intl.NumberFormat instanceof Function ) { + const intl = new Intl.NumberFormat(undefined, { + notation: 'compact', + maximumSignificantDigits: 3 + }); + if ( + intl.resolvedOptions instanceof Function && + intl.resolvedOptions().hasOwnProperty('notation') + ) { + intlNumberFormat = intl; + } + } + } + return n > 10000 && intlNumberFormat instanceof Object + ? intlNumberFormat.format(n) + : n.toLocaleString(); + }; + + const updateCount = function(cm) { + const state = getSearchState(cm); + const count = state.lines.length; + const span = state.widget.querySelector( + '.cm-search-widget-count > span:nth-of-type(2)' + ); + span.textContent = formatNumber(count); + span.title = count.toLocaleString(); + }; + + const updateRank = function(cm) { + const state = getSearchState(cm); + const lines = state.lines; + const current = cm.getCursor().line; + let l = 0; + let r = lines.length; + let i = -1; + while ( l < r ) { + i = l + r >>> 1; + const candidate = lines[i]; + if ( current === candidate ) { break; } + if ( current < candidate ) { + r = i; + } else /* if ( current > candidate ) */ { + l = i + 1; + } + } + let text = ''; + if ( i !== -1 ) { + text = formatNumber(i + 1); + if ( lines[i] !== current ) { + text = '~' + text; + } + text = text + '\xA0/\xA0'; + } + const span = state.widget.querySelector( + '.cm-search-widget-count > span:nth-of-type(1)' + ); + span.textContent = text; + }; - function startSearch(cm, state) { + const startSearch = function(cm, state) { state.query = parseQuery(state.queryText); - if ( state.overlay ) { + if ( state.overlay !== undefined ) { cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query)); } state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query)); cm.addOverlay(state.overlay); - if ( cm.showMatchesOnScrollbar ) { - if ( state.annotate ) { + if ( state.dirty ) { + self.searchThread.setHaystack(cm.getValue()); + state.dirty = false; + } + self.searchThread.search(state.query).then(lines => { + if ( Array.isArray(lines) === false ) { return; } + state.lines = lines; + const count = lines.length; + updateRank(cm); + updateCount(cm); + if ( state.annotate !== undefined ) { state.annotate.clear(); - state.annotate = null; + state.annotate = undefined; } - state.annotate = cm.showMatchesOnScrollbar( - state.query, - queryCaseInsensitive(state.query), - { multiline: false } - ); - let count = state.annotate.matches.length; - state.widget - .querySelector('.cm-search-widget-count > span:nth-of-type(2)') - .textContent = count > 1000 ? '1000+' : count; - state.widget.setAttribute('data-query', state.queryText); - // Ensure the caret is visible - let input = state.widget.querySelector('.cm-search-widget-input > input'); - input.selectionStart = input.selectionStart; - } - } + if ( count === 0 ) { return; } + state.annotate = cm.annotateScrollbar('CodeMirror-search-match'); + const annotations = []; + let lineBeg = -1; + let lineEnd = -1; + for ( const line of lines ) { + if ( lineBeg === -1 ) { + lineBeg = line; + lineEnd = line + 1; + continue; + } else if ( line === lineEnd ) { + lineEnd = line + 1; + continue; + } + annotations.push({ + from: { line: lineBeg, ch: 0 }, + to: { line: lineEnd, ch: 0 } + }); + lineBeg = -1; + } + if ( lineBeg !== -1 ) { + annotations.push({ + from: { line: lineBeg, ch: 0 }, + to: { line: lineEnd, ch: 0 } + }); + } + state.annotate.update(annotations); + }); + state.widget.setAttribute('data-query', state.queryText); + // Ensure the caret is visible + let input = state.widget.querySelector('.cm-search-widget-input > input'); + input.selectionStart = input.selectionStart; + }; - function findNext(cm, dir, callback) { + const findNext = function(cm, dir, callback) { cm.operation(function() { - var state = getSearchState(cm); + const state = getSearchState(cm); if ( !state.query ) { return; } - var cursor = getSearchCursor( + let cursor = getSearchCursor( cm, state.query, dir <= 0 ? cm.getCursor('from') : cm.getCursor('to') ); - let previous = dir < 0; + const previous = dir < 0; if (!cursor.find(previous)) { cursor = getSearchCursor( cm, state.query, - previous ? CodeMirror.Pos(cm.lastLine()) : CodeMirror.Pos(cm.firstLine(), 0) + previous + ? CodeMirror.Pos(cm.lastLine()) + : CodeMirror.Pos(cm.firstLine(), 0) ); if (!cursor.find(previous)) return; } @@ -243,21 +358,22 @@ cm.scrollIntoView({from: cursor.from(), to: cursor.to()}, 20); if (callback) callback(cursor.from(), cursor.to()); }); - } + }; - function clearSearch(cm, hard) { + const clearSearch = function(cm, hard) { cm.operation(function() { - var state = getSearchState(cm); + const state = getSearchState(cm); if ( state.query ) { state.query = state.queryText = null; } - if ( state.overlay ) { + state.lines = []; + if ( state.overlay !== undefined ) { cm.removeOverlay(state.overlay); - state.overlay = null; + state.overlay = undefined; } if ( state.annotate ) { state.annotate.clear(); - state.annotate = null; + state.annotate = undefined; } state.widget.removeAttribute('data-query'); if ( hard ) { @@ -267,15 +383,15 @@ cm.state.search = null; } }); - } + }; - function findCommit(cm, dir) { - var state = getSearchState(cm); + const findCommit = function(cm, dir) { + const state = getSearchState(cm); if ( state.queryTimer !== null ) { clearTimeout(state.queryTimer); state.queryTimer = null; } - var queryText = queryTextFromSearchWidget(cm); + const queryText = queryTextFromSearchWidget(cm); if ( queryText === state.queryText ) { return; } state.queryText = queryText; if ( state.queryText === '' ) { @@ -286,12 +402,12 @@ findNext(cm, dir); }); } - } + }; - function findCommand(cm) { - var queryText = cm.getSelection() || undefined; + const findCommand = function(cm) { + let queryText = cm.getSelection() || undefined; if ( !queryText ) { - var word = cm.findWordAt(cm.getCursor()); + const word = cm.findWordAt(cm.getCursor()); queryText = cm.getRange(word.anchor, word.head); if ( /^\W|\W$/.test(queryText) ) { queryText = undefined; @@ -300,17 +416,17 @@ } queryTextToSearchWidget(cm, queryText); findCommit(cm, 1); - } + }; - function findNextCommand(cm) { - var state = getSearchState(cm); + const findNextCommand = function(cm) { + const state = getSearchState(cm); if ( state.query ) { return findNext(cm, 1); } - } + }; - function findPrevCommand(cm) { - var state = getSearchState(cm); + const findPrevCommand = function(cm) { + const state = getSearchState(cm); if ( state.query ) { return findNext(cm, -1); } - } + }; { const searchWidgetTemplate = @@ -318,13 +434,13 @@ '
' + 'search ' + '' + - '' + + '' + '' + - '0' + + '0' + '' + ' ' + - 'angle-up ' + - 'angle-up ' + + 'angle-up ' + + 'angle-up' + 'external-link' + '
' + ''; @@ -341,4 +457,4 @@ CodeMirror.defineInitHook(function(cm) { getSearchState(cm); }); -}); +})(self.CodeMirror); diff --git a/src/js/reverselookup.js b/src/js/reverselookup.js index 7d9af5a9a8da4..7f39244feb871 100644 --- a/src/js/reverselookup.js +++ b/src/js/reverselookup.js @@ -457,7 +457,6 @@ if ( return new Promise(resolve => { pendingResponses.set(id, resolve); }); - }; // This tells the worker that filter lists may have changed. diff --git a/src/lib/codemirror/addon/search/matchesonscrollbar.js b/src/lib/codemirror/addon/search/matchesonscrollbar.js deleted file mode 100644 index 8a4a82758495b..0000000000000 --- a/src/lib/codemirror/addon/search/matchesonscrollbar.js +++ /dev/null @@ -1,97 +0,0 @@ -// CodeMirror, copyright (c) by Marijn Haverbeke and others -// Distributed under an MIT license: https://codemirror.net/LICENSE - -(function(mod) { - if (typeof exports == "object" && typeof module == "object") // CommonJS - mod(require("../../lib/codemirror"), require("./searchcursor"), require("../scroll/annotatescrollbar")); - else if (typeof define == "function" && define.amd) // AMD - define(["../../lib/codemirror", "./searchcursor", "../scroll/annotatescrollbar"], mod); - else // Plain browser env - mod(CodeMirror); -})(function(CodeMirror) { - "use strict"; - - CodeMirror.defineExtension("showMatchesOnScrollbar", function(query, caseFold, options) { - if (typeof options == "string") options = {className: options}; - if (!options) options = {}; - return new SearchAnnotation(this, query, caseFold, options); - }); - - function SearchAnnotation(cm, query, caseFold, options) { - this.cm = cm; - this.options = options; - var annotateOptions = {listenForChanges: false}; - for (var prop in options) annotateOptions[prop] = options[prop]; - if (!annotateOptions.className) annotateOptions.className = "CodeMirror-search-match"; - this.annotation = cm.annotateScrollbar(annotateOptions); - this.query = query; - this.caseFold = caseFold; - this.gap = {from: cm.firstLine(), to: cm.lastLine() + 1}; - this.matches = []; - this.update = null; - - this.findMatches(); - this.annotation.update(this.matches); - - var self = this; - cm.on("change", this.changeHandler = function(_cm, change) { self.onChange(change); }); - } - - var MAX_MATCHES = 1000; - - SearchAnnotation.prototype.findMatches = function() { - if (!this.gap) return; - for (var i = 0; i < this.matches.length; i++) { - var match = this.matches[i]; - if (match.from.line >= this.gap.to) break; - if (match.to.line >= this.gap.from) this.matches.splice(i--, 1); - } - var cursor = this.cm.getSearchCursor(this.query, CodeMirror.Pos(this.gap.from, 0), {caseFold: this.caseFold, multiline: this.options.multiline}); - var maxMatches = this.options && this.options.maxMatches || MAX_MATCHES; - while (cursor.findNext()) { - var match = {from: cursor.from(), to: cursor.to()}; - if (match.from.line >= this.gap.to) break; - this.matches.splice(i++, 0, match); - if (this.matches.length > maxMatches) break; - } - this.gap = null; - }; - - function offsetLine(line, changeStart, sizeChange) { - if (line <= changeStart) return line; - return Math.max(changeStart, line + sizeChange); - } - - SearchAnnotation.prototype.onChange = function(change) { - var startLine = change.from.line; - var endLine = CodeMirror.changeEnd(change).line; - var sizeChange = endLine - change.to.line; - if (this.gap) { - this.gap.from = Math.min(offsetLine(this.gap.from, startLine, sizeChange), change.from.line); - this.gap.to = Math.max(offsetLine(this.gap.to, startLine, sizeChange), change.from.line); - } else { - this.gap = {from: change.from.line, to: endLine + 1}; - } - - if (sizeChange) for (var i = 0; i < this.matches.length; i++) { - var match = this.matches[i]; - var newFrom = offsetLine(match.from.line, startLine, sizeChange); - if (newFrom != match.from.line) match.from = CodeMirror.Pos(newFrom, match.from.ch); - var newTo = offsetLine(match.to.line, startLine, sizeChange); - if (newTo != match.to.line) match.to = CodeMirror.Pos(newTo, match.to.ch); - } - clearTimeout(this.update); - var self = this; - this.update = setTimeout(function() { self.updateAfterChange(); }, 250); - }; - - SearchAnnotation.prototype.updateAfterChange = function() { - this.findMatches(); - this.annotation.update(this.matches); - }; - - SearchAnnotation.prototype.clear = function() { - this.cm.off("change", this.changeHandler); - this.annotation.clear(); - }; -}); diff --git a/src/whitelist.html b/src/whitelist.html index 2a1dbe022fd53..b06744c742a11 100644 --- a/src/whitelist.html +++ b/src/whitelist.html @@ -41,12 +41,12 @@ - + From 128c6174a3c960135bb74ff93c985d40b1e56f4e Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 2 Aug 2020 12:32:40 -0400 Subject: [PATCH 28/38] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 03046e8ef8e3c..58977e6289991 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.28.5.10 +1.28.5.11 From cff589637cb13172beed7042d7cf9763a1834da9 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 2 Aug 2020 12:40:49 -0400 Subject: [PATCH 29/38] Make Firefox dev build auto-update --- dist/firefox/updates.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index 641f897230ef5..bef079469f0a1 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,10 +3,10 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.28.5.10", + "version": "1.28.5.11", "browser_specific_settings": { "gecko": { "strict_min_version": "55" } }, - "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b10", - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b10/uBlock0_1.28.5b10.firefox.signed.xpi" + "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b11", + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b11/uBlock0_1.28.5b11.firefox.signed.xpi" } ] } From d654a5d6cfab0fe607726cdfc2f7557419da0276 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 2 Aug 2020 12:46:52 -0400 Subject: [PATCH 30/38] Fix search operation broken by search worker going away Related commit: - https://github.com/gorhill/uBlock/commit/23332400f5f16de1a54265dd7bb110ea7e1da57c Since the search worker can go away after its time-to-live elapsed, we may need to pass again the haystack on which search operations are performed. --- src/js/codemirror/search-thread.js | 6 +++++- src/js/codemirror/search.js | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/js/codemirror/search-thread.js b/src/js/codemirror/search-thread.js index 29d4465d6b9dd..c3eda6de8c9dc 100644 --- a/src/js/codemirror/search-thread.js +++ b/src/js/codemirror/search-thread.js @@ -157,6 +157,10 @@ if ( }, { once: true }); }; + const needHaystack = function() { + return worker instanceof Object === false; + }; + const setHaystack = function(content) { init(); worker.postMessage({ what: 'setHaystack', content }); @@ -183,7 +187,7 @@ if ( }); }; - self.searchThread = { setHaystack, search, shutdown }; + self.searchThread = { needHaystack, setHaystack, search, shutdown }; } /******************************************************************************/ diff --git a/src/js/codemirror/search.js b/src/js/codemirror/search.js index cc91b742d21f1..4c0f7e86cab24 100644 --- a/src/js/codemirror/search.js +++ b/src/js/codemirror/search.js @@ -286,7 +286,7 @@ } state.overlay = searchOverlay(state.query, queryCaseInsensitive(state.query)); cm.addOverlay(state.overlay); - if ( state.dirty ) { + if ( state.dirty || self.searchThread.needHaystack() ) { self.searchThread.setHaystack(cm.getValue()); state.dirty = false; } From eba9f82dcc40b17d10be7516525d54933456170a Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 2 Aug 2020 12:49:05 -0400 Subject: [PATCH 31/38] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 58977e6289991..763adc698307a 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.28.5.11 +1.28.5.12 From 6f4e12ac5d8fb7997a0f17cf2abc3d974da6a528 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 2 Aug 2020 13:05:34 -0400 Subject: [PATCH 32/38] Make Firefox dev build auto-update --- dist/firefox/updates.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index bef079469f0a1..ffd5960db299e 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,10 +3,10 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.28.5.11", + "version": "1.28.5.12", "browser_specific_settings": { "gecko": { "strict_min_version": "55" } }, - "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b11", - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b11/uBlock0_1.28.5b11.firefox.signed.xpi" + "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b12", + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b12/uBlock0_1.28.5b12.firefox.signed.xpi" } ] } From 50bf999a1219b3d2e4e132d86d063e0b270605df Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 3 Aug 2020 08:55:02 -0400 Subject: [PATCH 33/38] Fine tune CodeMirror editor's search widget Code review following latest changes. Also, move the input field to the left so that it renders properly on smaller displays and does not jump around when the result position/count numbers change. This also makes it easier to add more functionality to the editor's toolbar in the future. --- src/css/codemirror.css | 37 +++++++++++------------------- src/js/codemirror/search-thread.js | 9 +++++--- src/js/codemirror/search.js | 36 ++++++++++------------------- 3 files changed, 32 insertions(+), 50 deletions(-) diff --git a/src/css/codemirror.css b/src/css/codemirror.css index e17b5caa3f6bf..b7e598ecc5c42 100644 --- a/src/css/codemirror.css +++ b/src/css/codemirror.css @@ -64,14 +64,17 @@ div.CodeMirror span.CodeMirror-matchingbracket { direction: ltr; display: flex; flex-shrink: 0; - font-size: 110%; - justify-content: center; + justify-content: space-between; padding: 0.5em; user-select: none; -moz-user-select: none; -webkit-user-select: none; z-index: 1000; } +.cm-search-widget-input { + display: inline-flex; + flex-grow: 1; + } .cm-search-widget .fa-icon { fill: #888; font-size: 140%; @@ -79,37 +82,25 @@ div.CodeMirror span.CodeMirror-matchingbracket { .cm-search-widget .fa-icon:not(.fa-icon-ro):hover { fill: #000; } -.cm-search-widget-input { - border: 1px solid gray; - border-radius: 3px; +.cm-search-widget-input input { display: inline-flex; - min-width: 14em; - width: 30vw; - } -.cm-search-widget-input > input { - border: 0; flex-grow: 1; - width: 100%; + max-width: 16em; } -.cm-search-widget-input > .cm-search-widget-count { +.cm-search-widget-count { align-items: center; - display: none; + display: inline-flex; flex-grow: 0; - font-size: 80%; - padding: 0 0.4em; + font-size: 95%; + min-width: 6em; + visibility: hidden; } -.cm-search-widget[data-query] .cm-search-widget-count { - display: inline-flex; +.cm-search-widget[data-query] .cm-search-widget-count:not(:empty) { + visibility: visible; } .cm-search-widget .cm-search-widget-button:hover { color: #000; } -.cm-search-widget .sourceURL { - padding-left: 0.5em; - padding-right: 0.5em; - position: absolute; - left: 0; - } .cm-search-widget .sourceURL[href=""] { display: none; } diff --git a/src/js/codemirror/search-thread.js b/src/js/codemirror/search-thread.js index c3eda6de8c9dc..7b33fb126589f 100644 --- a/src/js/codemirror/search-thread.js +++ b/src/js/codemirror/search-thread.js @@ -152,9 +152,6 @@ if ( clearTimeout(workerTTLTimer); } workerTTLTimer = vAPI.setTimeout(shutdown, workerTTL); - self.addEventListener('beforeunload', ( ) => { - destroy(); - }, { once: true }); }; const needHaystack = function() { @@ -187,6 +184,12 @@ if ( }); }; + self.addEventListener( + 'beforeunload', + ( ) => { destroy(); }, + { once: true } + ); + self.searchThread = { needHaystack, setHaystack, search, shutdown }; } diff --git a/src/js/codemirror/search.js b/src/js/codemirror/search.js index 4c0f7e86cab24..9da1b401ceac6 100644 --- a/src/js/codemirror/search.js +++ b/src/js/codemirror/search.js @@ -135,7 +135,7 @@ } }); cm.on('cursorActivity', cm => { - updateRank(cm); + updateCount(cm); }); }; @@ -239,16 +239,6 @@ }; const updateCount = function(cm) { - const state = getSearchState(cm); - const count = state.lines.length; - const span = state.widget.querySelector( - '.cm-search-widget-count > span:nth-of-type(2)' - ); - span.textContent = formatNumber(count); - span.title = count.toLocaleString(); - }; - - const updateRank = function(cm) { const state = getSearchState(cm); const lines = state.lines; const current = cm.getCursor().line; @@ -273,10 +263,11 @@ } text = text + '\xA0/\xA0'; } - const span = state.widget.querySelector( - '.cm-search-widget-count > span:nth-of-type(1)' - ); + const count = lines.length; + text += formatNumber(count); + const span = state.widget.querySelector('.cm-search-widget-count'); span.textContent = text; + span.title = count.toLocaleString(); }; const startSearch = function(cm, state) { @@ -294,7 +285,6 @@ if ( Array.isArray(lines) === false ) { return; } state.lines = lines; const count = lines.length; - updateRank(cm); updateCount(cm); if ( state.annotate !== undefined ) { state.annotate.clear(); @@ -330,7 +320,7 @@ }); state.widget.setAttribute('data-query', state.queryText); // Ensure the caret is visible - let input = state.widget.querySelector('.cm-search-widget-input > input'); + const input = state.widget.querySelector('.cm-search-widget-input input'); input.selectionStart = input.selectionStart; }; @@ -432,15 +422,13 @@ const searchWidgetTemplate = ''; From 3fed25a52da5120c71d10bc94fc404ab50581764 Mon Sep 17 00:00:00 2001 From: C0rn3j Date: Mon, 27 Jul 2020 14:41:14 +0200 Subject: [PATCH 34/38] Use ISO8061 dates in filter comments --- src/js/storage.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/js/storage.js b/src/js/storage.js index a6a546a4e0526..957294755fa4a 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -387,10 +387,13 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { this.hiddenSettings.autoCommentFilterTemplate.indexOf('{{') !== -1 ) { const d = new Date(); + // Date in YYYY-MM-DD format - https://stackoverflow.com/a/50130338 + const ISO8061Date = new Date(d.getTime() + + (d.getTimezoneOffset()*60000)).toISOString().split('T')[0]; comment = '! ' + this.hiddenSettings.autoCommentFilterTemplate - .replace('{{date}}', d.toLocaleDateString()) + .replace('{{date}}', ISO8061Date) .replace('{{time}}', d.toLocaleTimeString()) .replace('{{origin}}', options.origin); } @@ -532,7 +535,7 @@ self.addEventListener('hiddenSettingsChanged', ( ) => { vAPI.storage.get('availableFilterLists'), this.assets.metadata(), ]); - + oldAvailableLists = bin && bin.availableFilterLists || {}; for ( const assetKey in entries ) { From c8127ec3bf0e5bcf49a1163e066b4e4496a5d1dc Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 3 Aug 2020 10:39:17 -0400 Subject: [PATCH 35/38] Fix typo as per https://github.com/uBlockOrigin/uBlock-issues/issues/1191 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 84855a6808d97..5d59b93494ee8 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ There is also a development version if you want to test uBlock Origin with the l uBlock Origin is compatible with [SeaMonkey](http://www.seamonkey-project.org/), [Pale Moon](https://www.palemoon.org/), and possibly other browsers based on Firefox: for installation, see [Install / Firefox legacy](https://github.com/gorhill/uBlock/blob/master/dist/README.md#firefox-legacy). -uBO mat also be installable as a [Debian package](https://packages.debian.org/stable/source/ublock-origin): +uBO may also be installed as a [Debian package](https://packages.debian.org/stable/source/ublock-origin): - Firefox 56-: `apt-get install xul-ext-ublock-origin` - Firefox 55+: `apt-get install webext-ublock-origin` From 811852bda2d9c70e07be2c4ee846f44a08fa0cbb Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 3 Aug 2020 10:41:06 -0400 Subject: [PATCH 36/38] New revision for dev build --- dist/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/version b/dist/version index 763adc698307a..ff5df5723800f 100644 --- a/dist/version +++ b/dist/version @@ -1 +1 @@ -1.28.5.12 +1.28.5.13 From 9c653341c152d8d0e594284a320e3cc574f61cb0 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 3 Aug 2020 10:45:34 -0400 Subject: [PATCH 37/38] Make Firefox dev build auto-update --- dist/firefox/updates.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/firefox/updates.json b/dist/firefox/updates.json index ffd5960db299e..058958ed642d7 100644 --- a/dist/firefox/updates.json +++ b/dist/firefox/updates.json @@ -3,10 +3,10 @@ "uBlock0@raymondhill.net": { "updates": [ { - "version": "1.28.5.12", + "version": "1.28.5.13", "browser_specific_settings": { "gecko": { "strict_min_version": "55" } }, - "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b12", - "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b12/uBlock0_1.28.5b12.firefox.signed.xpi" + "update_info_url": "https://github.com/gorhill/uBlock/releases/tag/1.28.5b13", + "update_link": "https://github.com/gorhill/uBlock/releases/download/1.28.5b13/uBlock0_1.28.5b13.firefox.signed.xpi" } ] } From 59496cfa45b841c6def476ac0e31f4329a1ba539 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 3 Aug 2020 14:30:30 -0400 Subject: [PATCH 38/38] Restore visual of input field on Firefox --- src/css/codemirror.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/css/codemirror.css b/src/css/codemirror.css index b7e598ecc5c42..7f1668c57f203 100644 --- a/src/css/codemirror.css +++ b/src/css/codemirror.css @@ -83,6 +83,7 @@ div.CodeMirror span.CodeMirror-matchingbracket { fill: #000; } .cm-search-widget-input input { + border: 1px solid gray; display: inline-flex; flex-grow: 1; max-width: 16em;