diff --git a/assets/assets.json b/assets/assets.json index ede40f04c5a1d..9fd1a81645120 100644 --- a/assets/assets.json +++ b/assets/assets.json @@ -74,6 +74,13 @@ "assets/ublock/resource-abuse.txt" ] }, + "ublock-annoyances": { + "content": "filters", + "group": "default", + "title": "uBlock filters – Annoyances", + "off": true, + "contentURL": "https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/annoyances.txt" + }, "ublock-unbreak": { "content": "filters", "group": "default", @@ -417,7 +424,7 @@ "off": true, "title": "IRN: Adblock-Iran", "lang": "fa", - "contentURL": "https://raw.githubusercontent.com/farrokhi/adblock-iran/master/filter.txt", + "contentURL": "https://cdn.rawgit.com/farrokhi/adblock-iran/master/filter.txt", "supportURL": "https://github.com/farrokhi/adblock-iran" }, "ISL-0": { @@ -585,14 +592,14 @@ "contentURL": "https://raw.githubusercontent.com/betterwebleon/slovenian-list/master/filters.txt", "supportURL": "https://github.com/betterwebleon/slovenian-list" }, - "SWE-0": { + "SWE-1": { "content": "filters", "group": "regions", "off": true, - "title": "SWE: Fanboy's Swedish", + "title": "SWE: Frellwit's Swedish Filter", "lang": "sv", - "contentURL": "https://www.fanboy.co.nz/fanboy-swedish.txt", - "supportURL": "https://github.com/ryanbr/fanboy-adblock/issues" + "contentURL": "https://raw.githubusercontent.com/lassekongo83/Frellwits-filter-lists/master/Frellwits-Swedish-Filter.txt", + "supportURL": "https://github.com/lassekongo83/Frellwits-filter-lists" }, "TUR-0": { "content": "filters", diff --git a/dist/README.md b/dist/README.md index 6932c8e95f96c..49acc042a3b43 100644 --- a/dist/README.md +++ b/dist/README.md @@ -22,7 +22,7 @@ Remember that you have to update manually also. For some users, updating manuall ### Firefox webext -Compatible with Firefox 52 and beyond. This works only if you set `xpinstall.signatures.required` to `false` in `about:config`. +Compatible with Firefox 52 and beyond. This works only if you set `xpinstall.signatures.required` to `false` in `about:config`.[see "Add-on signing in Firefox"](https://support.mozilla.org/en-US/kb/add-on-signing-in-firefox) - Download `ublock0.webext.xpi` ([latest release desirable](https://github.com/gorhill/uBlock/releases)). - Right-click and choose _"Save As..."_. @@ -39,7 +39,7 @@ Compatible with Firefox 24 to Firefox 56. - Download `ublock0.firefox.xpi` ([latest release desirable](https://github.com/gorhill/uBlock/releases)). - Drag and drop the previously downloaded `ublock0.firefox.xpi` into Firefox -With Firefox 43 and beyond, you may need to toggle the setting `xpinstall.signatures.required` to `false` in `about:config`. +With Firefox 43 and beyond, you may need to toggle the setting `xpinstall.signatures.required` to `false` in `about:config`.[see "Add-on signing in Firefox"](https://support.mozilla.org/en-US/kb/add-on-signing-in-firefox) Your uBlock Origin settings are kept intact even after you uninstall the addon. diff --git a/dist/description/description-fa.txt b/dist/description/description-fa.txt index b204bd8b4c5d0..1fba6ef815c7b 100644 --- a/dist/description/description-fa.txt +++ b/dist/description/description-fa.txt @@ -2,11 +2,11 @@ بررسی تصویری از کارایی این محصول: https://github.com/gorhill/uBlock/wiki/uBlock-vs.-ABP:-efficiency-compared -کاربرد: دکمه ی پاور بزرگ در پنجره برای فعال یا غیر فعال کردن uBlock برای صفحه ی جاری است. فقط برای همین سایت اعمال میشود، دکمه ی پاوری برای تمام سایت ها نیست. +روش استفاده: دکمۀ قدرت بزرگ در پنجرۀ بالاپَر برای فعال یا غیرفعال کردن دائمی یوبلاک برای وب‌سایت فعلی می‌باشد. این فقط برای همین سایت اعمال میشود، این دکمه ی قدرتی برای تمام سایت ها نیست. *** -انعطاف پذیری آن بیشتر از "ad blocker" است: همچنین می تواند فیلتر ها را از هاست میزبان، بخواند و بسازد. +انعطاف پذیری آن بیشتر از "ad blocker" است: این یکی همچنین می تواند فیلتر‌هایی را از فایل‌های هاست‌های میزبان، خوانده و بسازد. بیرون از جعبه، این لیست فیلترها بارگذاری و اجرا میشوند: diff --git a/dist/description/description-kk.txt b/dist/description/description-kk.txt new file mode 100644 index 0000000000000..0407a694837a8 --- /dev/null +++ b/dist/description/description-kk.txt @@ -0,0 +1,49 @@ +An efficient blocker: easy on memory and CPU footprint, and yet can load and enforce thousands more filters than other popular blockers out there. + +Illustrated overview of its efficiency: https://github.com/gorhill/uBlock/wiki/uBlock-vs.-ABP:-efficiency-compared + +Usage: The big power button in the popup is to permanently disable/enable uBlock for the current web site. It applies to the current web site only, it is not a global power button. + +*** + +Flexible, it's more than an "ad blocker": it can also read and create filters from hosts files. + +Out of the box, these lists of filters are loaded and enforced: + +- EasyList +- Peter Lowe’s Ad server list +- EasyPrivacy +- Malware domains + +More lists are available for you to select if you wish: + +- Fanboy’s Enhanced Tracking List +- Dan Pollock’s hosts file +- hpHosts’s Ad and tracking servers +- MVPS HOSTS +- Spam404 +- And many others + +Of course, the more filters enabled, the higher the memory footprint. Yet, even after adding Fanboy's two extra lists, hpHosts’s Ad and tracking servers, uBlock still has a lower memory footprint than other very popular blockers out there. + +Also, be aware that selecting some of these extra lists may lead to higher likelihood of web site breakage -- especially those lists which are normally used as hosts file. + +*** + +Without the preset lists of filters, this extension is nothing. So if ever you really do want to contribute something, think about the people working hard to maintain the filter lists you are using, which were made available to use by all for free. + +*** + +Еркін. +Open source with public license (GPLv3) +For users by users. + +Contributors @ Github: https://github.com/gorhill/uBlock/graphs/contributors +Contributors @ Crowdin: https://crowdin.net/project/ublock + +*** + +It's quite an early version, keep this in mind when you review. + +Project change log: +https://github.com/gorhill/uBlock/releases diff --git a/dist/description/description-no.txt b/dist/description/description-nb.txt similarity index 100% rename from dist/description/description-no.txt rename to dist/description/description-nb.txt diff --git a/platform/chromium/manifest.json b/platform/chromium/manifest.json index b5f610a8aa44b..ab31d6feddaf8 100644 --- a/platform/chromium/manifest.json +++ b/platform/chromium/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "uBlock Origin", - "version": "1.14.22", + "version": "1.15.4", "commands": { "launch-element-zapper": { diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 83bae3229e9f4..21c3dbcfb1004 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2017 The uBlock Origin authors + Copyright (C) 2014-2018 The uBlock Origin authors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -384,32 +384,34 @@ vAPI.tabs.registerListeners = function() { /******************************************************************************/ +// Caller must be prepared to deal with nil tab argument. + +// https://code.google.com/p/chromium/issues/detail?id=410868#c8 + vAPI.tabs.get = function(tabId, callback) { - var onTabReady = function(tab) { - // https://code.google.com/p/chromium/issues/detail?id=410868#c8 - if ( chrome.runtime.lastError ) { - /* noop */ - } - // Caller must be prepared to deal with nil tab value - callback(tab); - }; + if ( tabId === null ) { + chrome.tabs.query( + { active: true, currentWindow: true }, + function(tabs) { + if ( chrome.runtime.lastError ) { /* noop */ } + callback( + Array.isArray(tabs) && tabs.length !== 0 ? tabs[0] : null + ); + } + ); + return; + } - if ( tabId !== null ) { - tabId = toChromiumTabId(tabId); - if ( tabId === 0 ) { - onTabReady(null); - } else { - chrome.tabs.get(tabId, onTabReady); - } + tabId = toChromiumTabId(tabId); + if ( tabId === 0 ) { + callback(null); return; } - var onTabReceived = function(tabs) { - // https://code.google.com/p/chromium/issues/detail?id=410868#c8 - void chrome.runtime.lastError; - callback(tabs[0]); - }; - chrome.tabs.query({ active: true, currentWindow: true }, onTabReceived); + chrome.tabs.get(tabId, function(tab) { + if ( chrome.runtime.lastError ) { /* noop */ } + callback(tab); + }); }; /******************************************************************************/ @@ -579,11 +581,9 @@ vAPI.tabs.remove = function(tabId) { /******************************************************************************/ -vAPI.tabs.reload = function(tabId /*, flags*/) { +vAPI.tabs.reload = function(tabId, bypassCache) { tabId = toChromiumTabId(tabId); - if ( tabId === 0 ) { - return; - } + if ( tabId === 0 ) { return; } var onReloaded = function() { // https://code.google.com/p/chromium/issues/detail?id=410868#c8 @@ -592,7 +592,11 @@ vAPI.tabs.reload = function(tabId /*, flags*/) { } }; - chrome.tabs.reload(tabId, onReloaded); + chrome.tabs.reload( + tabId, + { bypassCache: bypassCache === true }, + onReloaded + ); }; /******************************************************************************/ @@ -777,8 +781,7 @@ vAPI.messaging.onPortMessage = (function() { var toFramework = function(request, port, callback) { var sender = port && port.sender; if ( !sender ) { return; } - var tabId = sender.tab && sender.tab.id; - if ( !tabId ) { return; } + var tabId = sender.tab && sender.tab.id || undefined; var msg = request.msg, toPort; switch ( msg.what ) { @@ -786,7 +789,7 @@ vAPI.messaging.onPortMessage = (function() { case 'connectionRefused': toPort = messaging.ports.get(msg.fromToken); if ( toPort !== undefined ) { - msg.tabId = tabId.toString(); + msg.tabId = tabId && tabId.toString(); toPort.postMessage(request); } else { msg.what = 'connectionBroken'; @@ -794,7 +797,7 @@ vAPI.messaging.onPortMessage = (function() { } break; case 'connectionRequested': - msg.tabId = '' + tabId.toString(); + msg.tabId = tabId && tabId.toString(); for ( toPort of messaging.ports.values() ) { toPort.postMessage(request); } @@ -806,7 +809,7 @@ vAPI.messaging.onPortMessage = (function() { port.name === msg.fromToken ? msg.toToken : msg.fromToken ); if ( toPort !== undefined ) { - msg.tabId = tabId.toString(); + msg.tabId = tabId && tabId.toString(); toPort.postMessage(request); } else { msg.what = 'connectionBroken'; @@ -814,6 +817,7 @@ vAPI.messaging.onPortMessage = (function() { } break; case 'userCSS': + if ( tabId === undefined ) { break; } var details = { code: undefined, frameId: sender.frameId, diff --git a/platform/edge/manifest.json b/platform/edge/manifest.json index 570e77ac2941c..c6120d2c98619 100644 --- a/platform/edge/manifest.json +++ b/platform/edge/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "uBlock Origin", - "version": "1.14.24", + "version": "1.15.4", "commands": { "launch-element-zapper": { diff --git a/platform/edge/vapi-background.js b/platform/edge/vapi-background.js index eccfdf479d79e..139918569215c 100644 --- a/platform/edge/vapi-background.js +++ b/platform/edge/vapi-background.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2016 The uBlock Origin authors + Copyright (C) 2014-2018 The uBlock Origin authors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -536,32 +536,34 @@ vAPI.tabs.registerListeners = function() { /******************************************************************************/ +// Caller must be prepared to deal with nil tab argument. + +// https://code.google.com/p/chromium/issues/detail?id=410868#c8 + vAPI.tabs.get = function(tabId, callback) { - var onTabReady = function(tab) { - // https://code.google.com/p/chromium/issues/detail?id=410868#c8 - if ( browser.runtime.lastError ) { - /* noop */ - } - // Caller must be prepared to deal with nil tab value - callback(tab); - }; + if ( tabId === null ) { + browser.tabs.query( + { active: true, currentWindow: true }, + function(tabs) { + if ( browser.runtime.lastError ) { /* noop */ } + callback( + Array.isArray(tabs) && tabs.length !== 0 ? tabs[0] : null + ); + } + ); + return; + } - if ( tabId !== null ) { - tabId = toEdgeTabId(tabId); - if ( tabId === 0 ) { - onTabReady(null); - } else { - browser.tabs.get(tabId, onTabReady); - } + tabId = toEdgeTabId(tabId); + if ( tabId === 0 ) { + callback(null); return; } - var onTabReceived = function(tabs) { - // https://code.google.com/p/chromium/issues/detail?id=410868#c8 - void browser.runtime.lastError; - callback(tabs[0]); - }; - browser.tabs.query({ active: true, currentWindow: true }, onTabReceived); + browser.tabs.get(tabId, function(tab) { + if ( browser.runtime.lastError ) { /* noop */ } + callback(tab); + }); }; /******************************************************************************/ @@ -731,7 +733,7 @@ vAPI.tabs.remove = function(tabId) { /******************************************************************************/ -vAPI.tabs.reload = function(tabId /*, flags*/) { +vAPI.tabs.reload = function(tabId, bypassCache) { tabId = toEdgeTabId(tabId); if ( tabId === 0 ) { return; @@ -745,7 +747,7 @@ vAPI.tabs.reload = function(tabId /*, flags*/) { /* noop */ return; } - vAPI.tabs.injectScript(tabId, { code: 'window.location.reload()' }); + vAPI.tabs.injectScript(tabId, { code: `window.location.reload(${!!bypassCache})` }); }); }; @@ -935,8 +937,7 @@ vAPI.messaging.onPortMessage = (function() { var toFramework = function(request, port, callback) { var sender = port && port.sender; if ( !sender ) { return; } - var tabId = sender.tab && sender.tab.id; - if ( !tabId ) { return; } + var tabId = sender.tab && sender.tab.id || undefined; var msg = request.msg, toPort; switch ( msg.what ) { @@ -944,7 +945,7 @@ vAPI.messaging.onPortMessage = (function() { case 'connectionRefused': toPort = messaging.ports.get(msg.fromToken); if ( toPort !== undefined ) { - msg.tabId = tabId.toString(); + msg.tabId = tabId && tabId.toString(); toPort.postMessage(request); } else { msg.what = 'connectionBroken'; @@ -952,7 +953,7 @@ vAPI.messaging.onPortMessage = (function() { } break; case 'connectionRequested': - msg.tabId = '' + tabId.toString(); + msg.tabId = tabId && tabId.toString(); for ( toPort of messaging.ports.values() ) { toPort.postMessage(request); } @@ -964,7 +965,7 @@ vAPI.messaging.onPortMessage = (function() { port.name === msg.fromToken ? msg.toToken : msg.fromToken ); if ( toPort !== undefined ) { - msg.tabId = tabId.toString(); + msg.tabId = tabId && tabId.toString(); toPort.postMessage(request); } else { msg.what = 'connectionBroken'; @@ -972,6 +973,7 @@ vAPI.messaging.onPortMessage = (function() { } break; case 'userCSS': + if ( tabId === undefined ) { break; } var details = { code: undefined, frameId: sender.frameId, diff --git a/platform/opera/manifest.json b/platform/opera/manifest.json index 316e49036f957..9aa58f523ff6b 100644 --- a/platform/opera/manifest.json +++ b/platform/opera/manifest.json @@ -1,64 +1,86 @@ { - "author": "All uBlock Origin contributors", - "browser_action": { - "default_icon": { - "19": "img/browsericons/icon19.png", - "38": "img/browsericons/icon19.png" - }, - "default_title": "uBlock Origin", - "default_popup": "popup.html" + "author": "All uBlock Origin contributors", + "background": { + "page": "background.html" + }, + "browser_action": { + "default_icon": { + "19": "img/browsericons/icon19.png", + "38": "img/browsericons/icon19.png" }, - "background": { - "page": "background.html" + "default_popup": "popup.html", + "default_title": "uBlock Origin" + }, + "commands": { + "launch-element-picker": { + "description": "__MSG_popupTipPicker__" }, - "commands": { - "launch-element-zapper": { - "description": "__MSG_popupTipZapper__" - }, - "launch-element-picker": { - "description": "__MSG_popupTipPicker__" - }, - "launch-logger": { - "description": "__MSG_popupTipLog__" - } + "launch-element-zapper": { + "description": "__MSG_popupTipZapper__" }, - "content_scripts": [ - { - "matches": ["http://*/*", "https://*/*"], - "js": ["js/vapi.js", "js/vapi-client.js", "js/contentscript.js"], - "run_at": "document_start", - "all_frames": true - }, - { - "matches": ["http://*/*", "https://*/*"], - "js": ["js/scriptlets/subscriber.js"], - "run_at": "document_idle", - "all_frames": false - } - ], - "default_locale": "en", - "description": "__MSG_extShortDesc__", - "icons": { - "16": "img/icon_16.png", - "128": "img/icon_128.png" + "launch-logger": { + "description": "__MSG_popupTipLog__" + } + }, + "content_scripts": [ + { + "all_frames": true, + "js": [ + "js/vapi.js", + "js/vapi-client.js", + "js/contentscript.js" + ], + "matches": [ + "http://*/*", + "https://*/*" + ], + "run_at": "document_start" }, - "incognito": "split", - "manifest_version": 2, - "minimum_opera_version": "25.0", - "name": "uBlock Origin", - "optional_permissions": [ "file:///*" ], - "options_page": "dashboard.html", - "permissions": [ - "contextMenus", - "privacy", - "storage", - "tabs", - "unlimitedStorage", - "webNavigation", - "webRequest", - "webRequestBlocking", - "" - ], - "short_name": "uBlock₀", - "version": "1.9.15.101" + { + "all_frames": false, + "js": [ + "js/scriptlets/subscriber.js" + ], + "matches": [ + "http://*/*", + "https://*/*" + ], + "run_at": "document_idle" + } + ], + "default_locale": "en", + "description": "__MSG_extShortDesc__", + "icons": { + "128": "img/icon_128.png", + "16": "img/icon_16.png" + }, + "incognito": "split", + "manifest_version": 2, + "minimum_opera_version": "32.0", + "name": "uBlock Origin", + "optional_permissions": [ + "file:///*" + ], + "options_page": "dashboard.html", + "permissions": [ + "contextMenus", + "privacy", + "storage", + "tabs", + "unlimitedStorage", + "webNavigation", + "webRequest", + "webRequestBlocking", + "" + ], + "short_name": "uBlock\u2080", + "sidebar_action": { + "default_icon": { + "19": "img/browsericons/icon19.png", + "38": "img/browsericons/icon19.png" + }, + "default_panel": "logger-ui.html", + "default_title": "__MSG_statsPageName__" + }, + "version": "1.14.23.17" } diff --git a/platform/webext/manifest.json b/platform/webext/manifest.json index 70e97de5f9bd6..679ddbb1e8b4e 100644 --- a/platform/webext/manifest.json +++ b/platform/webext/manifest.json @@ -80,5 +80,13 @@ "" ], "short_name":"uBlock₀", + "sidebar_action":{ + "default_title":"__MSG_statsPageName__", + "default_panel":"logger-ui.html", + "default_icon":{ + "16":"img/ublock.svg", + "48":"img/ublock.svg" + } + }, "version":"1.9.15.101" } diff --git a/platform/webext/vapi-usercss.js b/platform/webext/vapi-usercss.js index b9ef068e341ce..9db9bce1b542a 100644 --- a/platform/webext/vapi-usercss.js +++ b/platform/webext/vapi-usercss.js @@ -204,9 +204,16 @@ vAPI.DOMFilterer.prototype = { var out = { declarative: [] }; + var selectors; for ( var entry of this.filterset ) { - if ( all === false && entry.internal ) { continue; } - out.declarative.push([ entry.selectors, entry.declarations ]); + 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; }, diff --git a/platform/webext/vapi-webrequest.js b/platform/webext/vapi-webrequest.js index 2caeb33e7bb51..e25b8d164e7c9 100644 --- a/platform/webext/vapi-webrequest.js +++ b/platform/webext/vapi-webrequest.js @@ -29,7 +29,11 @@ vAPI.net = { onBeforeRequest: {}, onBeforeMaybeSpuriousCSPReport: {}, onHeadersReceived: {}, - nativeCSPReportFiltering: true + nativeCSPReportFiltering: true, + webRequest: browser.webRequest, + canFilterResponseBody: + typeof browser.webRequest === 'object' && + typeof browser.webRequest.filterResponseData === 'function' }; /******************************************************************************/ @@ -57,46 +61,41 @@ vAPI.net.registerListeners = function() { var wrApi = browser.webRequest; // legacy Chromium understands only these network request types. - var validTypes = { - main_frame: true, - sub_frame: true, - stylesheet: true, - script: true, - image: true, - object: true, - xmlhttprequest: true, - other: true - }; + var validTypes = new Set([ + 'image', + 'main_frame', + 'object', + 'other', + 'script', + 'stylesheet', + 'sub_frame', + 'xmlhttprequest', + ]); // modern Chromium/WebExtensions: more types available. if ( wrApi.ResourceType ) { for ( let typeKey in wrApi.ResourceType ) { if ( wrApi.ResourceType.hasOwnProperty(typeKey) ) { - validTypes[wrApi.ResourceType[typeKey]] = true; + validTypes.add(wrApi.ResourceType[typeKey]); } } } var denormalizeTypes = function(aa) { if ( aa.length === 0 ) { - return Object.keys(validTypes); + return Array.from(validTypes); } - var out = []; - var i = aa.length, - type, - needOther = true; + var out = new Set(), + i = aa.length; while ( i-- ) { - type = aa[i]; - if ( validTypes[type] ) { - out.push(type); + var type = aa[i]; + if ( validTypes.has(type) ) { + out.add(type); } - if ( type === 'other' ) { - needOther = false; + if ( type === 'image' && validTypes.has('imageset') ) { + out.add('imageset'); } } - if ( needOther ) { - out.push('other'); - } - return out; + return Array.from(out); }; var punycode = self.punycode; @@ -140,7 +139,7 @@ vAPI.net.registerListeners = function() { let urls = this.onBeforeRequest.urls || ['']; let types = this.onBeforeRequest.types || undefined; if ( - (validTypes.websocket) && + (validTypes.has('websocket')) && (types === undefined || types.indexOf('websocket') !== -1) && (urls.indexOf('') === -1) ) { diff --git a/src/_locales/ar/messages.json b/src/_locales/ar/messages.json index 84ed339035d35..a6527c22c9402 100644 --- a/src/_locales/ar/messages.json +++ b/src/_locales/ar/messages.json @@ -48,7 +48,7 @@ "description": "English: Click: disable\/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page." }, "popupPowerSwitchInfo1": { - "message": "اضغط: لتعطيل\/تشغيل uBlock₀ لهذا الموقع.\n\nCtrl+click لتعطيل uBlock₀ لهذه الصفحة فقط.", + "message": "اضغط لتعطيل uBlock₀ لهذا الموقع.\n\nCtrl+click لتعطيل uBlock₀ لهذه الصفحة فقط.", "description": "Message to be read by screen readers" }, "popupPowerSwitchInfo2": { @@ -76,7 +76,7 @@ "description": "English: or" }, "popupTipDashboard": { - "message": "إضغط لفتح قائمة التحكم", + "message": "إضغط لفتح لوحة التحكم", "description": "English: Click to open the dashboard" }, "popupTipZapper": { @@ -100,7 +100,7 @@ "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups2": { - "message": "اضغط للسماح لجميع النوافذ المنبثقة لهذا الموقع", + "message": "أضغط للوقف منع جميع النوافذ المنبثقة لهذا الموقع", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoLargeMedia": { @@ -112,7 +112,7 @@ "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia2": { - "message": "اضغط للسماح لعناصر الوسائط الكبيرة لهذا الموقع", + "message": "أضغط للوقف منع عناصر الوسائط الكبيرة لهذا الموقع", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoCosmeticFiltering": { @@ -120,11 +120,11 @@ "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering1": { - "message": "اضغط لتعطيل الترشيح الجمالي لهذا الموقع", + "message": "اضغط لتعطيل الفلترة التجميلية لهذا الموقع", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering2": { - "message": "اضغط لتشغيل الترشيح الجمالي لهذا الموقع", + "message": "اضغط لتشغيل الفلترة التجميلية لهذا الموقع", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoRemoteFonts": { @@ -132,7 +132,7 @@ "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts1": { - "message": "أضغط للسماح للخطوط الخارجية لهذا الموقع", + "message": "أضغط لحجب الخطوط الخارجية لهذا الموقع", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts2": { @@ -384,7 +384,7 @@ "description": "English: One URL per line. Lines prefixed with ‘!’ will be ignored. Invalid URLs will be silently ignored." }, "3pExternalListObsolete": { - "message": "إنتهت صلاحيته.", + "message": "متقادم.", "description": "used as a tooltip for the out-of-date icon beside a list" }, "3pLastUpdate": { @@ -511,6 +511,10 @@ "message": "خلف الكواليس", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "علامة التبويب الحالية", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "فلتر سجل الإدخالات", "description": "English: filter log entries" diff --git a/src/_locales/bg/messages.json b/src/_locales/bg/messages.json index efdce303c4fb4..d50bd913b97c0 100644 --- a/src/_locales/bg/messages.json +++ b/src/_locales/bg/messages.json @@ -511,6 +511,10 @@ "message": "Скрити", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Последно активен раздел", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "филтриране на записи", "description": "English: filter log entries" @@ -716,7 +720,7 @@ "description": "tooltip" }, "cloudNoData": { - "message": "...\n...", + "message": "...", "description": "" }, "cloudDeviceNamePrompt": { diff --git a/src/_locales/bn/messages.json b/src/_locales/bn/messages.json index c4241726d980b..e99ea6f254ea4 100644 --- a/src/_locales/bn/messages.json +++ b/src/_locales/bn/messages.json @@ -511,6 +511,10 @@ "message": "পর্দার আড়ালে", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "বর্তমান ট্যাব", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "ফিল্টার লগের ভুক্তি", "description": "English: filter log entries" diff --git a/src/_locales/ca/messages.json b/src/_locales/ca/messages.json index 1e74724165ab2..a92f68e80a249 100644 --- a/src/_locales/ca/messages.json +++ b/src/_locales/ca/messages.json @@ -511,6 +511,10 @@ "message": "Peticions ocultes", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Pestanya actual", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtrar entrades del registre", "description": "English: filter log entries" diff --git a/src/_locales/cs/messages.json b/src/_locales/cs/messages.json index f3b7fc81fb639..d928a65981c48 100644 --- a/src/_locales/cs/messages.json +++ b/src/_locales/cs/messages.json @@ -4,7 +4,7 @@ "description": "extension name." }, "extShortDesc": { - "message": "Konečně efektivní blokovač, který nezatěžuje CPU a paměť.", + "message": "Konečně efektivní blokovač. Nezatěžuje CPU a paměť.", "description": "this will be in the chrome web store: must be 132 characters or less" }, "dashboardName": { @@ -48,11 +48,11 @@ "description": "English: Click: disable\/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page." }, "popupPowerSwitchInfo1": { - "message": "Click to disable uBlock₀ for this site.\n\nCtrl+click to disable uBlock₀ only on this page.", + "message": "Kliknutí: trvale zakázat uBlock₀ pro celý tento web.\n\nCtrl+kliknutí: zakázat uBlock₀ pouze pro tuto stránku.", "description": "Message to be read by screen readers" }, "popupPowerSwitchInfo2": { - "message": "Click to enable uBlock₀ for this site.", + "message": "Kliknutím povolíte uBlock₀ pro tento web.", "description": "Message to be read by screen readers" }, "popupBlockedRequestPrompt": { @@ -96,11 +96,11 @@ "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups1": { - "message": "Click to block all popups on this site", + "message": "Kliknutím zablokujete všechny popupy pro tento web", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups2": { - "message": "Click to no longer block all popups on this site", + "message": "Kliknutím vypnete blokování všech popupů pro tento web", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoLargeMedia": { @@ -108,11 +108,11 @@ "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia1": { - "message": "Click to block large media elements on this site", + "message": "Kliknutím zablokujete velké multimediální prvky na tomto webu", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia2": { - "message": "Click to no longer block large media elements on this site", + "message": "Kliknutím vypnete blokování velkých multimediálních prvků na tomto webu", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoCosmeticFiltering": { @@ -120,11 +120,11 @@ "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering1": { - "message": "Click to disable cosmetic filtering on this site", + "message": "Kliknutím zakážete kosmetické filtrování na tomto webu", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering2": { - "message": "Click to enable cosmetic filtering on this site", + "message": "Kliknutím povolíte kosmetické filtrování na tomto webu", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoRemoteFonts": { @@ -132,11 +132,11 @@ "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts1": { - "message": "Click to block remote fonts on this site", + "message": "Kliknutím zablokujete externí\/vzdálené fonty na tomto webu", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts2": { - "message": "Click to no longer block remote fonts on this site", + "message": "Kliknutím vypnete blokování externích\/vzdálených fontů na tomto webu", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipGlobalRules": { @@ -511,6 +511,10 @@ "message": "Za oponou", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Aktivní list", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtrovat záznamy", "description": "English: filter log entries" diff --git a/src/_locales/cv/messages.json b/src/_locales/cv/messages.json index 002c2cf52100c..1c37d33b5b20f 100644 --- a/src/_locales/cv/messages.json +++ b/src/_locales/cv/messages.json @@ -511,6 +511,10 @@ "message": "Behind the scene", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Current tab", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filter log entries", "description": "English: filter log entries" diff --git a/src/_locales/da/messages.json b/src/_locales/da/messages.json index ab3f2c14b8f17..e0921ec056944 100644 --- a/src/_locales/da/messages.json +++ b/src/_locales/da/messages.json @@ -48,11 +48,11 @@ "description": "English: Click: disable\/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page." }, "popupPowerSwitchInfo1": { - "message": "Click to disable uBlock₀ for this site.\n\nCtrl+click to disable uBlock₀ only on this page.", + "message": "Klik for at deaktivere uBlock₀ på dette websted.\n\nCtrl+Klik for at deaktivere uBlock₀ kun på denne side.", "description": "Message to be read by screen readers" }, "popupPowerSwitchInfo2": { - "message": "Click to enable uBlock₀ for this site.", + "message": "Klik for at aktivere uBlock₀ på dette websted.", "description": "Message to be read by screen readers" }, "popupBlockedRequestPrompt": { @@ -96,11 +96,11 @@ "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups1": { - "message": "Click to block all popups on this site", + "message": "Klik for at blokere alle popups på dette websted", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups2": { - "message": "Click to no longer block all popups on this site", + "message": "Klik for at stoppe med at blokere alle popups på dette websted", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoLargeMedia": { @@ -108,11 +108,11 @@ "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia1": { - "message": "Click to block large media elements on this site", + "message": "Klik for at blokere store medie-elementer på dette websted", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia2": { - "message": "Click to no longer block large media elements on this site", + "message": "Klik for at stoppe med at blokere store medie-elementer på dette websted", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoCosmeticFiltering": { @@ -120,11 +120,11 @@ "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering1": { - "message": "Click to disable cosmetic filtering on this site", + "message": "Klik for at deaktivere kosmetisk filtre på dette websted", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering2": { - "message": "Click to enable cosmetic filtering on this site", + "message": "Klik for at aktivere kosmetisk filtre på dette websted", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoRemoteFonts": { @@ -132,11 +132,11 @@ "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts1": { - "message": "Click to block remote fonts on this site", + "message": "Klik for at blokere skrifttyper fra nettet på dette websted", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts2": { - "message": "Click to no longer block remote fonts on this site", + "message": "Klik for at stoppe med at blokere skrifttyper på nettet på dette websted", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipGlobalRules": { @@ -292,7 +292,7 @@ "description": "" }, "settingsNoCSPReportsPrompt": { - "message": "Block CSP reports", + "message": "Blokér CSP rapporter", "description": "background information: https:\/\/github.com\/gorhill\/uBlock\/issues\/3150" }, "settingsStorageUsed": { @@ -364,7 +364,7 @@ "description": "English: Malware domains" }, "3pGroupAnnoyances": { - "message": "Annoyances", + "message": "Irriterende", "description": "The header identifying the filter lists in the category 'annoyances'" }, "3pGroupMultipurpose": { @@ -511,6 +511,10 @@ "message": "Bag kulisserne", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Current tab", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "Filtrér elementer i log", "description": "English: filter log entries" diff --git a/src/_locales/de/messages.json b/src/_locales/de/messages.json index 75a975d2f4c7a..e5b0ca59d6120 100644 --- a/src/_locales/de/messages.json +++ b/src/_locales/de/messages.json @@ -511,6 +511,10 @@ "message": "Hintergrundanfragen", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Aktueller Tab", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "Protokoll-Einträge filtern", "description": "English: filter log entries" diff --git a/src/_locales/el/messages.json b/src/_locales/el/messages.json index d43b84a73415f..6fb66ac467174 100644 --- a/src/_locales/el/messages.json +++ b/src/_locales/el/messages.json @@ -511,6 +511,10 @@ "message": "Παρασκήνιο", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Current tab", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "φιλτράρισμα των καταχωρήσεων", "description": "English: filter log entries" diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 0d02224110534..c0716de42ed35 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -511,6 +511,10 @@ "message":"Behind the scene", "description":"Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab":{ + "message":"Current tab", + "description":"Appears in the logger's tab selector" + }, "logFilterPrompt":{ "message":"filter log entries", "description": "English: filter log entries" diff --git a/src/_locales/eo/messages.json b/src/_locales/eo/messages.json index f6ed3e96f83b1..e88d8007568fe 100644 --- a/src/_locales/eo/messages.json +++ b/src/_locales/eo/messages.json @@ -511,6 +511,10 @@ "message": "Fonaj petoj", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Current tab", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtri protokolerojn", "description": "English: filter log entries" diff --git a/src/_locales/es/messages.json b/src/_locales/es/messages.json index cb47c4b825dfe..661157b011e45 100644 --- a/src/_locales/es/messages.json +++ b/src/_locales/es/messages.json @@ -511,6 +511,10 @@ "message": "Peticiones ocultas", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Pestaña actual", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtrar entradas del registro", "description": "English: filter log entries" diff --git a/src/_locales/et/messages.json b/src/_locales/et/messages.json index 9de2b2462ebb8..509beede24814 100644 --- a/src/_locales/et/messages.json +++ b/src/_locales/et/messages.json @@ -511,6 +511,10 @@ "message": "Telgitagus", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Praegune kaart", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtreeri logikirjeid", "description": "English: filter log entries" diff --git a/src/_locales/eu/messages.json b/src/_locales/eu/messages.json index 983e2e50ed25b..c40aa30d3139c 100644 --- a/src/_locales/eu/messages.json +++ b/src/_locales/eu/messages.json @@ -48,11 +48,11 @@ "description": "English: Click: disable\/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page." }, "popupPowerSwitchInfo1": { - "message": "Click to disable uBlock₀ for this site.\n\nCtrl+click to disable uBlock₀ only on this page.", + "message": "Klik: Gaitu\/ezgaitu uBlock₀ gune honetan.\n\nKtrl+klik: Desgaitu uBlock₀ orri honetan soilik.", "description": "Message to be read by screen readers" }, "popupPowerSwitchInfo2": { - "message": "Click to enable uBlock₀ for this site.", + "message": "Kilk: Gaitu uBlock₀ gune honetan.", "description": "Message to be read by screen readers" }, "popupBlockedRequestPrompt": { @@ -96,11 +96,11 @@ "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups1": { - "message": "Click to block all popups on this site", + "message": "Click: Blokeatu gune honetako laster leiho guztiak", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups2": { - "message": "Click to no longer block all popups on this site", + "message": "Click: Utzi gune honetako laster-leihoak blokeatzeari", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoLargeMedia": { @@ -108,11 +108,11 @@ "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia1": { - "message": "Click to block large media elements on this site", + "message": "Klik: Blokeatu gune honetako tamaina handiko elementuak", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia2": { - "message": "Click to no longer block large media elements on this site", + "message": "Klik: Utzi gune honetako tamaina handiko elementuak blokeatzeari", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoCosmeticFiltering": { @@ -120,11 +120,11 @@ "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering1": { - "message": "Click to disable cosmetic filtering on this site", + "message": "Klik: Desgaitu iragazki kosmetikoak gune honetan", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering2": { - "message": "Click to enable cosmetic filtering on this site", + "message": "Klik: Gaitu iragazki kosmetikoak gune honetan", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoRemoteFonts": { @@ -132,11 +132,11 @@ "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts1": { - "message": "Click to block remote fonts on this site", + "message": "Klik: Blokeatu urruneko letra-tipoak gune honetan", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts2": { - "message": "Click to no longer block remote fonts on this site", + "message": "Klik: Utzi urruneko letra-tipoak gune honetan blokeatzeari", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipGlobalRules": { @@ -292,7 +292,7 @@ "description": "" }, "settingsNoCSPReportsPrompt": { - "message": "Block CSP reports", + "message": "Blokeatu CSP txostenak", "description": "background information: https:\/\/github.com\/gorhill\/uBlock\/issues\/3150" }, "settingsStorageUsed": { @@ -511,6 +511,10 @@ "message": "Atzeko planoan", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Uneko fitxa", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "iragazi egunkariko sarrerak", "description": "English: filter log entries" diff --git a/src/_locales/fa/messages.json b/src/_locales/fa/messages.json index 0b11de1688da6..f2fd41ba3a86d 100644 --- a/src/_locales/fa/messages.json +++ b/src/_locales/fa/messages.json @@ -32,7 +32,7 @@ "description": "appears as tab name in dashboard" }, "statsPageName": { - "message": "uBlock₀ — لاگ درخواست های شبکه", + "message": "uBlock₀ — واقعه‌نگار", "description": "Title for the logger window" }, "aboutPageName": { @@ -48,11 +48,11 @@ "description": "English: Click: disable\/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page." }, "popupPowerSwitchInfo1": { - "message": "Click to disable uBlock₀ for this site.\n\nCtrl+click to disable uBlock₀ only on this page.", + "message": "کلیک کنید تا uBlock₀ برای این سایت غیر فعال شود.\n\nهمزمان کلیدکنترل را نگهدارید و کلیک کنید تا uBlock₀ فقط برای این صفحه غیر فعال شود.", "description": "Message to be read by screen readers" }, "popupPowerSwitchInfo2": { - "message": "Click to enable uBlock₀ for this site.", + "message": "کلیک کنید تا uBlock₀ برای این سایت فعال شود.", "description": "Message to be read by screen readers" }, "popupBlockedRequestPrompt": { @@ -80,7 +80,7 @@ "description": "English: Click to open the dashboard" }, "popupTipZapper": { - "message": "ورود به حالت انتخاب اشیاء", + "message": "ورود به حالت له کردن اشیاء", "description": "Tooltip for the element-zapper icon in the popup panel" }, "popupTipPicker": { @@ -88,59 +88,59 @@ "description": "English: Enter element picker mode" }, "popupTipLog": { - "message": "رفتن به لاگ درخواست", + "message": "بازکردن واقعه‌نگار", "description": "Tooltip used for the logger icon in the panel" }, "popupTipNoPopups": { - "message": "تغییر وضعیت انسداد همه ی پنجره های این سایت", + "message": "تغییر وضعیت انسداد همه‌ی پنجره های بالاپَر برای این سایت", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups1": { - "message": "Click to block all popups on this site", + "message": "کلیک کنید تا همۀ پنجره های بالاپَر در این سایت مسدود شوند", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups2": { - "message": "Click to no longer block all popups on this site", + "message": "کلیک کنید تا دیگر هیچ کدام از پنجره های بالاپَر این سایت مسدود نشوند", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoLargeMedia": { - "message": "تغییر وضعیت انسداد عناصر مدیای حجیم برای این سایت", + "message": "تغییر وضعیت مسدود کردن عناصر رسانه ای حجیم برای این سایت", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia1": { - "message": "Click to block large media elements on this site", + "message": "کلیک کنید تا عناصر رسانه ای حجیم بر روی این سایت مسدود شوند", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia2": { - "message": "Click to no longer block large media elements on this site", + "message": "کلیک کنید تا از این به بعد عناصر رسانه ای حجیم روی این سایت مسدود نشوند", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoCosmeticFiltering": { - "message": "تغییر وضعیت فیلترزیباسازی در این صفحه", + "message": "تغییر وضعیت فیلترزیباسازی برای این سایت", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering1": { - "message": "Click to disable cosmetic filtering on this site", + "message": "کلیک کنید تا فیلتر زیبا سازی بر روی این سایت غیر فعال شود", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering2": { - "message": "Click to enable cosmetic filtering on this site", + "message": "کلیک کنید تا فیلتر زیبا سازی بر روی این سایت فعال شود", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoRemoteFonts": { - "message": "تغییر وضعیت انسداد فونت های از راه دور برای این سایت", + "message": "تغییر وضعیت انسداد فونت های راه دور برای این سایت", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts1": { - "message": "Click to block remote fonts on this site", + "message": "کلیک کنید تا فونت های راه دور در این سایت مسدود شوند", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts2": { - "message": "Click to no longer block remote fonts on this site", + "message": "کلیک کنید تا از این به بعد فونت های راه دور در این سایت مسدود نشوند", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipGlobalRules": { - "message": "قوانین همگانی: این ستون برای قوانینی است که به تمامی سایت ها اعمال می شود.", + "message": "قوانین همگانی: این ستون برای قوانینی است که برای همۀ سایت ها اعمال می شوند.", "description": "Tooltip when hovering the top-most cell of the global-rules column." }, "popupTipLocalRules": { @@ -152,7 +152,7 @@ "description": "Tooltip when hovering over the padlock in the dynamic filtering pane." }, "popupTipRevertRules": { - "message": "برای برگشت تغییرات کلیک کنید.", + "message": "برای دور انداختن تغییراتی که اعمال کرده اید کلیک کنید.", "description": "Tooltip when hovering over the eraser in the dynamic filtering pane." }, "popupAnyRulePrompt": { @@ -260,15 +260,15 @@ "description": "For the tooltip of a link which gives access to advanced settings" }, "settingsPrefetchingDisabledPrompt": { - "message": "غیر فعال کردن واکشی اولیه (برای جلوگیری از هر گونه اتصال برای درخواست های شبکه مسدود شده)", + "message": "غیر فعال کردن واکشی اولیه (برای جلوگیری از هر گونه اتصال برای درخواست های مسدود شدۀ شبکه)", "description": "English: " }, "settingsHyperlinkAuditingDisabledPrompt": { - "message": "غیر فعال سازی پیوندهای نظارتی \/ هدایتی", + "message": "غیرفعال کردن حسابرسی پیوند", "description": "English: " }, "settingsWebRTCIPAddressHiddenPrompt": { - "message": "جلوگیری WebRTC از افشاسازی آدرس های IP محلی", + "message": "جلوگیری کردن از WebRTC از افشاسازی آدرس های IP محلی", "description": "English: " }, "settingPerSiteSwitchGroup": { @@ -276,23 +276,23 @@ "description": "" }, "settingPerSiteSwitchGroupSynopsis": { - "message": "این رفتارهای پیش فرض بسته به هر وب‌گاهیی می تواند باطل شود", + "message": "تنظیمات مخصوص هر سایت این تنظیمات پیش فرض را لغو می‌کند", "description": "" }, "settingsNoCosmeticFilteringPrompt": { - "message": "غیر فعال‌سازی انسداد تزیینی", + "message": "غیر فعال‌سازی فیلتر زیبا سازی", "description": "" }, "settingsNoLargeMediaPrompt": { - "message": "انسداد عناصر مدیایی حجیم تر از {{input:number}} کیلوبایت", + "message": "انسداد عناصر رسانه ای حجیم تر از {{input:number}} کیلوبایت", "description": "" }, "settingsNoRemoteFontsPrompt": { - "message": "انسداد فونت های از راه دور", + "message": "انسداد فونت های راه دور", "description": "" }, "settingsNoCSPReportsPrompt": { - "message": "Block CSP reports", + "message": "مسدود کردن براساس گزارشات سیاست امنیت محتوا", "description": "background information: https:\/\/github.com\/gorhill\/uBlock\/issues\/3150" }, "settingsStorageUsed": { @@ -332,15 +332,15 @@ "description": "English: Parse and enforce Adblock+ element hiding filters." }, "3pParseAllABPHideFiltersInfo": { - "message": "

این گزینه Adblock Plus-compatible “element hiding” filters<\/a> را تجزیه و اجرا میکند. این فیلتر ها اساساً فنی هستند، آنها اشیاء موجود در صفحه وب را که مزاحم تلقی میشوند و با موتور فیلترینگ شبکه قابل بلاک کردن نیستند را حذف میکنند.<\/p>

فعال کردن این قابلیت مصرف حافظه ی uBlock<\/i>را افزایش میدهد.<\/p>", + "message": "

این گزینه فیلترهای “مخفی‌کردن اشیا” سازگار با ادبلاک پلاس<\/a> را تجزیه و اجرا میکند. این فیلتر ها اساساً فنی هستند، آنها اشیاء موجود در صفحه وب را که مزاحم تلقی میشوند و با موتور فیلترینگ شبکه قابل بلاک کردن نیستند را حذف میکنند.<\/p>

فعال کردن این قابلیت مصرف حافظه ی uBlock را افزایش میدهد.<\/p>", "description": "Describes the purpose of the 'Parse and enforce cosmetic filters' feature." }, "3pIgnoreGenericCosmeticFilters": { - "message": "Ignore generic cosmetic filters", + "message": "نادیده گرفتن فیلترهای زیبا سازی عمومی", "description": "This will cause uBO to ignore all generic cosmetic filters." }, "3pIgnoreGenericCosmeticFiltersInfo": { - "message": "

Generic cosmetic filters are those cosmetic filters which are meant to apply on all web sites.

Though handled efficiently by uBlock₀, generic cosmetic filters may still contribute measurable memory and CPU overhead on some web pages, especially for large and long-lived ones.

Enabling this option will eliminate the memory and CPU overhead added to web pages as a result of handling generic cosmetic filters, and also lower the memory footprint of uBlock₀ itself.

It is recommended to enable this option on less powerful devices.", + "message": "

فیلترهای زیبا سازی عمومی، آن دسته از فیلترهای زیبا سازی هستند که در همه وب سایت ها کاربرد دارند.\n

اگر چه بصورت موثر توسط uBlock₀ انجام شده، ولی فیلترهای عمومی زیبا سازی هنوز هم ممکن است سربار حافظه قابل اندازه گیری و سربار پردازنده در برخی از صفحات وب به جای بگذارد، به ویژه برای صفحات وب بزرگ و طولانی مدت.

فعال کردن این گزینه سربارهای حافظه و پردازنده را که به صفحات اضافه شده به عنوان نتیجۀ اجرای فیلترهای عمومی زیبا سازی برطرف می‌کند، و همچنین ردپای حافظۀ خود uBlock₀ را کمتر می‌کند

توصیه می شود این گزینه را در دستگاه های با قدرت کمتر فعال کنید.", "description": "Describes the purpose of the 'Ignore generic cosmetic filters' feature." }, "3pListsOfBlockedHostsHeader": { @@ -364,7 +364,7 @@ "description": "English: Malware domains" }, "3pGroupAnnoyances": { - "message": "Annoyances", + "message": "مزاحم‌ها", "description": "The header identifying the filter lists in the category 'annoyances'" }, "3pGroupMultipurpose": { @@ -384,11 +384,11 @@ "description": "English: One URL per line. Lines prefixed with ‘!’ will be ignored. Invalid URLs will be silently ignored." }, "3pExternalListObsolete": { - "message": "منقضی شده", + "message": "منقضی شده.", "description": "used as a tooltip for the out-of-date icon beside a list" }, "3pLastUpdate": { - "message": "آخرین به روز رسانی: {{ago}}", + "message": "آخرین به روز رسانی: {{ago}}.\nبرای یک به روز رسانی اجباری کلیک کنید.", "description": "used as a tooltip for the clock icon beside a list" }, "3pUpdating": { @@ -396,7 +396,7 @@ "description": "used as a tooltip for the spinner icon beside a list" }, "3pNetworkError": { - "message": "یک خطای شبکه از بروزشدن منابع جلوگیری کرد.", + "message": "یک خطای شبکه از بروزشدن این منبع جلوگیری کرد.", "description": "used as a tooltip for error icon beside a list" }, "1pFormatHint": { @@ -468,7 +468,7 @@ "description": "English: dynamic rule syntax and full documentation." }, "whitelistPrompt": { - "message": "لیست شما از هاست هایی که uBlock₀ در آنها غیرفعال خواهد شد. یک ورودی در هر خط. هاست های نامعتبر بدون اشاره ای نادیده گرفته می شوند.", + "message": "دستور العمل‌های لیست سفید حکم می‌کند که uBlock₀ باید بر روی کدام یک از صفحات وب غیر فعال باشد. در هر خط فقط یک مورد. دستور العمل‌های نامعتبر بدون اشاره ای نادیده گرفته شده و بیرون انداخته می‌شوند.", "description": "English: An overview of the content of the dashboard's Whitelist pane." }, "whitelistImport": { @@ -511,6 +511,10 @@ "message": "پشت صحنه", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "برگۀ فعلی", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "فیلتر کردن مطالب لاگ", "description": "English: filter log entries" @@ -528,7 +532,7 @@ "description": "Label for the type selector" }, "loggerURLFilteringHeader": { - "message": "فیلترینگ آدرس اینترنتی پویا", + "message": "فیلترینگ پویای آدرس اینترنتی", "description": "Small header to identify the dynamic URL filtering section" }, "loggerStaticFilteringHeader": { @@ -536,7 +540,7 @@ "description": "Small header to identify the static filtering section" }, "loggerStaticFilteringSentence": { - "message": "{{action}} درخواست های شبکه از {{type}} {{br}} هر آدرس اینترنتی مطابقت داده می شود {{url}} {{br}}و هر چه شد{{origin}},{{br}}{{importance}} یک فیلتر تطبیق استثنا وجود دارد.", + "message": "{{action}} درخواست های شبکه از نوع {{type}}{{br}} که مطابقت دارد با آدرس اینترنی {{url}}{{br}} و سرچشمه می‌گیرد از {{origin}}،{{br}}{{importance}} اینها یک فیلتر استثناء از تطبیق هستند.", "description": "Used in the static filtering wizard" }, "loggerStaticFilteringSentencePartBlock": { @@ -572,7 +576,7 @@ "description": "Used in the static filtering wizard" }, "loggerStaticFilteringFinderSentence1": { - "message": "انسداد ایستا {{filter}} یاقت شد در:", + "message": "انسداد ایستا {{filter}} یافت شد در:", "description": "Below this sentence, the filter lists in which the filter was found" }, "aboutChangelog": { @@ -620,15 +624,15 @@ "description": "Message to display when an error occurred during restore" }, "aboutResetDataConfirm": { - "message": "تمام تنظیمات شده حذف و uBlock دوباره راه اندازی خواهد شد.\n\nتنظیم مجدد uBlock به تنظیمات کارخانه؟", + "message": "تمام تنظیمات شما حذف شده و uBlock₀ دوباره راه اندازی خواهد شد.\n\nتنظیم مجدد uBlock₀ به تنظیمات کارخانه؟", "description": "Message asking user to confirm reset" }, "errorCantConnectTo": { - "message": "قادر به اتصال به {{url}}", + "message": "خطای شبکه: {{msg}}", "description": "English: Network error: {{msg}}" }, "subscriberConfirm": { - "message": "uBlock₀: لینک زیر به لیست فیلتر ها اضافه شود؟\n\nعنوان: \"{{title}}\"\nآدرس: {{url}}", + "message": "uBlock₀: آدرس اینترنتی زیر به فهرست فیلتر های سفارشی شما اضافه شود؟\n\nعنوان: \"{{title}}\"\nآدرس: {{url}}", "description": "English: The message seen by the user to confirm subscription to a ABP filter list" }, "elapsedOneMinuteAgo": { @@ -660,7 +664,7 @@ "description": "Firefox\/Fennec-specific: Show Dashboard" }, "showNetworkLogButton": { - "message": "نمایش درخواست ثبت شبکه", + "message": "نمایش واقعه‌نگار", "description": "Firefox\/Fennec-specific: Show Logger" }, "fennecMenuItemBlockingOff": { @@ -668,7 +672,7 @@ "description": "Firefox-specific: appears as 'uBlock₀ (off)'" }, "docblockedPrompt1": { - "message": "ublock از بارگذاری این صفحات جلوگیری کرده:", + "message": "uBlock₀ از بارگذاری این صفحات جلوگیری کرده:", "description": "English: uBlock₀ has prevented the following page from loading:" }, "docblockedPrompt2": { @@ -708,11 +712,11 @@ "description": "tooltip" }, "cloudPull": { - "message": "ورود از فضای ذخیره سازی ابری", + "message": "وارد کردن از فضای ذخیره سازی ابری", "description": "tooltip" }, "cloudPullAndMerge": { - "message": "ورود از فضای ذخیره سازی ابری و ادغام با تنظیمات فعلی", + "message": "وارد کردن از فضای ذخیره سازی ابری و ادغام با تنظیمات فعلی", "description": "tooltip" }, "cloudNoData": { @@ -728,7 +732,7 @@ "description": "A warning to users at the top of 'Advanced settings' page" }, "genericSubmit": { - "message": "ثبت", + "message": "ارسال", "description": "for generic 'Submit' buttons" }, "genericApplyChanges": { @@ -736,7 +740,7 @@ "description": "for generic 'Apply changes' buttons" }, "genericRevert": { - "message": "بازگشت", + "message": "بازگرداندن", "description": "for generic 'Revert' buttons" }, "genericBytes": { @@ -744,7 +748,7 @@ "description": "" }, "contextMenuTemporarilyAllowLargeMediaElements": { - "message": "اجازه موقت عناصر مدیای حجیم", + "message": "اجازه موقت عناصر رسانه‌ای حجیم", "description": "A context menu entry, present when large media elements have been blocked on the current site" }, "dummy": { diff --git a/src/_locales/fi/messages.json b/src/_locales/fi/messages.json index a00212a955a18..9aec3a8859ff3 100644 --- a/src/_locales/fi/messages.json +++ b/src/_locales/fi/messages.json @@ -511,6 +511,10 @@ "message": "Kulissien takana", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Current tab", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "suodata lokimerkinnät", "description": "English: filter log entries" diff --git a/src/_locales/fil/messages.json b/src/_locales/fil/messages.json index bebc7f404bdce..39786e77cd7a7 100644 --- a/src/_locales/fil/messages.json +++ b/src/_locales/fil/messages.json @@ -511,6 +511,10 @@ "message": "Behind the scene", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Current tab", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filter log entries", "description": "English: filter log entries" diff --git a/src/_locales/fr/messages.json b/src/_locales/fr/messages.json index 1b84357596230..5e7eebd468549 100644 --- a/src/_locales/fr/messages.json +++ b/src/_locales/fr/messages.json @@ -511,6 +511,10 @@ "message": "Requêtes en coulisses", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Onglet courant", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "Filtrer les entrées du journal", "description": "English: filter log entries" diff --git a/src/_locales/fy/messages.json b/src/_locales/fy/messages.json index 44e5a14314fae..27cf13485da71 100644 --- a/src/_locales/fy/messages.json +++ b/src/_locales/fy/messages.json @@ -511,6 +511,10 @@ "message": "Achter de skermen", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Aktuele ljepblêd", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "lochboekitems filterje", "description": "English: filter log entries" diff --git a/src/_locales/gl/messages.json b/src/_locales/gl/messages.json index 40cee338de996..c0f122e08c047 100644 --- a/src/_locales/gl/messages.json +++ b/src/_locales/gl/messages.json @@ -511,6 +511,10 @@ "message": "Peticións ocultas", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Lapela activa", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtras as entradas de rexistro", "description": "English: filter log entries" diff --git a/src/_locales/he/messages.json b/src/_locales/he/messages.json index bc00586e8764f..ab66313334d76 100644 --- a/src/_locales/he/messages.json +++ b/src/_locales/he/messages.json @@ -511,6 +511,10 @@ "message": "מאחורי הקלעים", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "לשונית נוכחית", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "סנן רשומות", "description": "English: filter log entries" diff --git a/src/_locales/hi/messages.json b/src/_locales/hi/messages.json index 9f02e4d59c2bc..efe448d4aafe0 100644 --- a/src/_locales/hi/messages.json +++ b/src/_locales/hi/messages.json @@ -48,11 +48,11 @@ "description": "English: Click: disable\/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page." }, "popupPowerSwitchInfo1": { - "message": "Click to disable uBlock₀ for this site.\n\nCtrl+click to disable uBlock₀ only on this page.", + "message": "दबाए: uBlock को इस साईट में बंद करने के लिए।\n\nCtrl के साथ दबाए: uBlock को केवल इस पेज में बंद करने के लिए।", "description": "Message to be read by screen readers" }, "popupPowerSwitchInfo2": { - "message": "Click to enable uBlock₀ for this site.", + "message": "इस साईट में uBlock₀ को चालू करने के लिए क्लिक कीजिये।", "description": "Message to be read by screen readers" }, "popupBlockedRequestPrompt": { @@ -68,7 +68,7 @@ "description": "Example: 15 or 13%" }, "popupBlockedSinceInstallPrompt": { - "message": "इंस्टॉल से अब तक", + "message": "स्थापना से अब तक", "description": "English: since install" }, "popupOr": { @@ -80,7 +80,7 @@ "description": "English: Click to open the dashboard" }, "popupTipZapper": { - "message": "Enter element zapper mode", + "message": "अन्श मिटाने की प्रक्रिया आरम्भ करे", "description": "Tooltip for the element-zapper icon in the popup panel" }, "popupTipPicker": { @@ -96,11 +96,11 @@ "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups1": { - "message": "Click to block all popups on this site", + "message": "इस वेबसाइट पर सभी पॉप-अप्स को ब्लाक करने के लिए क्लिक करें", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups2": { - "message": "Click to no longer block all popups on this site", + "message": "इस वेबसाइट पर सभी पॉप-अप्स को ब्लाक न करने के लिए क्लिक करें", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoLargeMedia": { @@ -108,11 +108,11 @@ "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia1": { - "message": "Click to block large media elements on this site", + "message": "इस वेबसाइट पर बड़े मीडिया ऐलिमेंटस को ब्लाक करने के लिए क्लिक करें", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia2": { - "message": "Click to no longer block large media elements on this site", + "message": "इस वेबसाइट पर बड़े मीडिया ऐलिमेंटस को ब्लाक न करने के लिए क्लिक करें", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoCosmeticFiltering": { @@ -296,11 +296,11 @@ "description": "background information: https:\/\/github.com\/gorhill\/uBlock\/issues\/3150" }, "settingsStorageUsed": { - "message": "Storage used: {{value}} bytes", + "message": "प्रचलित मैमोरी: {{value}} बाइट्स", "description": "English: Storage used: {{}} bytes" }, "settingsLastRestorePrompt": { - "message": "Last restore:", + "message": "अंतिम बहाल:", "description": "English: Last restore:" }, "settingsLastBackupPrompt": { @@ -320,11 +320,11 @@ "description": "A checkbox in the _3rd-party filters_ pane" }, "3pUpdateNow": { - "message": "अभी अपडेट करें", + "message": "अभी नवीकृत करें", "description": "A button in the in the _3rd-party filters_ pane" }, "3pPurgeAll": { - "message": "Purge all caches", + "message": "सभी अस्थायी मेमोरी को शुद्ध करे", "description": "A button in the in the _3rd-party filters_ pane" }, "3pParseAllABPHideFiltersPrompt1": { @@ -364,7 +364,7 @@ "description": "English: Malware domains" }, "3pGroupAnnoyances": { - "message": "Annoyances", + "message": "सतानेवाले विज्ञापन", "description": "The header identifying the filter lists in the category 'annoyances'" }, "3pGroupMultipurpose": { @@ -388,11 +388,11 @@ "description": "used as a tooltip for the out-of-date icon beside a list" }, "3pLastUpdate": { - "message": "Last update: {{ago}}.\nClick to force an update.", + "message": "अन्तिम अद्यातन: {{ago}}. कृत्रिम नवीकरण के लिए क्लिक की जिए", "description": "used as a tooltip for the clock icon beside a list" }, "3pUpdating": { - "message": "Updating...", + "message": "नवीकरण प्रगति में हैं...", "description": "used as a tooltip for the spinner icon beside a list" }, "3pNetworkError": { @@ -448,7 +448,7 @@ "description": "Will discard manually-edited content and exit manual-edit mode" }, "rulesImport": { - "message": "Import from file...", + "message": "फाइल से आयात करे...", "description": "" }, "rulesExport": { @@ -511,6 +511,10 @@ "message": "पर्दे के पीछे", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "वर्तमान टैब", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filter log entries", "description": "English: filter log entries" @@ -520,11 +524,11 @@ "description": "Tooltip informaing that the input field is to set the maximum number of entries in the log" }, "loggerURLFilteringContextLabel": { - "message": "Context:", + "message": "सन्दर्भ:", "description": "Label for the context selector" }, "loggerURLFilteringTypeLabel": { - "message": "प्रकार", + "message": "प्रकार:", "description": "Label for the type selector" }, "loggerURLFilteringHeader": { @@ -564,11 +568,11 @@ "description": "Used in the static filtering wizard" }, "loggerStaticFilteringSentencePartNotImportant": { - "message": "except when", + "message": "सिवाय", "description": "Used in the static filtering wizard" }, "loggerStaticFilteringSentencePartImportant": { - "message": "even if", + "message": "भले ही", "description": "Used in the static filtering wizard" }, "loggerStaticFilteringFinderSentence1": { @@ -576,7 +580,7 @@ "description": "Below this sentence, the filter lists in which the filter was found" }, "aboutChangelog": { - "message": "Change log", + "message": "परिवर्तन सूची", "description": "English: Change log" }, "aboutWiki": { @@ -664,11 +668,11 @@ "description": "Firefox\/Fennec-specific: Show Logger" }, "fennecMenuItemBlockingOff": { - "message": "off", + "message": "बंद", "description": "Firefox-specific: appears as 'uBlock₀ (off)'" }, "docblockedPrompt1": { - "message": "uBlock Origin has prevented the following page from loading:", + "message": "uBlock Origin ने इस पेज को चालु होने से रोका हैं:", "description": "English: uBlock₀ has prevented the following page from loading:" }, "docblockedPrompt2": { @@ -680,15 +684,15 @@ "description": "label to be used for the parameter-less URL: https:\/\/cloud.githubusercontent.com\/assets\/585534\/9832014\/bfb1b8f0-593b-11e5-8a27-fba472a5529a.png" }, "docblockedFoundIn": { - "message": "Found in:", + "message": "इन में पायी है:", "description": "English: List of filter list names follows" }, "docblockedBack": { - "message": "Go back", + "message": "वापस जाएँ", "description": "English: Go back" }, "docblockedClose": { - "message": "Close this window", + "message": "इस विंडो को बंद करे", "description": "English: Close this window" }, "docblockedProceed": { @@ -696,11 +700,11 @@ "description": "English: Disable strict blocking for {{hostname}} ..." }, "docblockedDisableTemporary": { - "message": "Temporarily", + "message": "कुछ समय के लिए", "description": "English: Temporarily" }, "docblockedDisablePermanent": { - "message": "हमेशा", + "message": "स्थायी", "description": "English: Permanently" }, "cloudPush": { @@ -720,7 +724,7 @@ "description": "" }, "cloudDeviceNamePrompt": { - "message": "This device name:", + "message": "इस साधन का नाम:", "description": "used as a prompt for the user to provide a custom device name" }, "advancedSettingsWarning": { @@ -732,7 +736,7 @@ "description": "for generic 'Submit' buttons" }, "genericApplyChanges": { - "message": "Apply changes", + "message": "परिवर्तन लागू करें", "description": "for generic 'Apply changes' buttons" }, "genericRevert": { @@ -744,7 +748,7 @@ "description": "" }, "contextMenuTemporarilyAllowLargeMediaElements": { - "message": "Temporarily allow large media elements", + "message": "कुछ समय के लिए विशाल तत्वोंको चलने की अनुमति दे", "description": "A context menu entry, present when large media elements have been blocked on the current site" }, "dummy": { diff --git a/src/_locales/hr/messages.json b/src/_locales/hr/messages.json index b09cc5867c810..fb888cb69b4c7 100644 --- a/src/_locales/hr/messages.json +++ b/src/_locales/hr/messages.json @@ -511,6 +511,10 @@ "message": "Iza scene", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Trenutna kartica", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtrirajte zabilješke", "description": "English: filter log entries" diff --git a/src/_locales/hu/messages.json b/src/_locales/hu/messages.json index 71551b31babb2..a1b3efd1d2e00 100644 --- a/src/_locales/hu/messages.json +++ b/src/_locales/hu/messages.json @@ -48,11 +48,11 @@ "description": "English: Click: disable\/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page." }, "popupPowerSwitchInfo1": { - "message": "Kattints a uBlock₀ letiltásához ezen a webhelyen.\n\nCtrl+kattintás a uBlock₀ letiltásához csak a jelenlegi oldalon.", + "message": "Kattints a uBlock₀ letiltásához ezen a webhelyen.\n\nCtrl+klikk a uBlock₀ letiltásához csak a jelenlegi oldalon.", "description": "Message to be read by screen readers" }, "popupPowerSwitchInfo2": { - "message": "Klikk a uBlock₀ engedélyezéséhez ezen a webhelyen.", + "message": "Kattints az uBlock₀ engedélyezéséhez ezen a webhelyen.", "description": "Message to be read by screen readers" }, "popupBlockedRequestPrompt": { @@ -511,6 +511,10 @@ "message": "Hálózati lekérések a háttérben", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Jelenlegi lap", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "naplóbejegyzések szűrése", "description": "English: filter log entries" diff --git a/src/_locales/id/messages.json b/src/_locales/id/messages.json index 9bf1fc25b4135..97201ef9c761d 100644 --- a/src/_locales/id/messages.json +++ b/src/_locales/id/messages.json @@ -228,7 +228,7 @@ "description": "English: Block element" }, "settingsCollapseBlockedPrompt": { - "message": "Sembunyikan tempat elemen yang diblokir", + "message": "Sembunyikan wadah elemen yang diblokir", "description": "English: Hide placeholders of blocked elements" }, "settingsIconBadgePrompt": { @@ -511,6 +511,10 @@ "message": "Di balik layar", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Tab saat ini", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "saring entri catatan", "description": "English: filter log entries" diff --git a/src/_locales/it/messages.json b/src/_locales/it/messages.json index 1c11c43ed2031..ad4ab65ca84d1 100644 --- a/src/_locales/it/messages.json +++ b/src/_locales/it/messages.json @@ -511,6 +511,10 @@ "message": "Dietro le quinte", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Scheda corrente", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtra voci del registro", "description": "English: filter log entries" diff --git a/src/_locales/ja/messages.json b/src/_locales/ja/messages.json index 20ca6aa68dc24..3832289bc498e 100644 --- a/src/_locales/ja/messages.json +++ b/src/_locales/ja/messages.json @@ -336,11 +336,11 @@ "description": "Describes the purpose of the 'Parse and enforce cosmetic filters' feature." }, "3pIgnoreGenericCosmeticFilters": { - "message": "要素隠蔽フィルターを無視する", + "message": "汎用的な要素隠蔽フィルターを無視する", "description": "This will cause uBO to ignore all generic cosmetic filters." }, "3pIgnoreGenericCosmeticFiltersInfo": { - "message": "

要素隠蔽フィルターは、ほぼすべてのウェブサイトに適用することができるフィルターです。

これはuBlock₀によって効率的に処理されますが、特に大規模で長期運営されている一部のウェブページ上ではメモリやCPUの処理に時間がかかる可能性があります。

このオプションを有効にすると、要素隠蔽フィルターの処理結果としてウェブページに追加されたメモリやCPUの処理時間を減らし、uBlock₀自体のメモリ使用量を軽減できます。

低スペックの環境では、このオプションを有効にすることを推奨します。", + "message": "

汎用的な要素隠蔽フィルターとは、すべてのウェブサイトに適用される要素隠蔽フィルターのことです。

これはuBlock₀によって効率的に処理されますが、特に大規模で長期運営されている一部のウェブページ上では、メモリ使用量や処理時間に目に見える影響を与えることがあります。

このオプションを有効にすると、汎用的な要素隠蔽フィルターを処理するためにウェブページごとに追加されるメモリ使用量や処理時間を減らし、さらにuBlock₀自体のメモリ使用量を軽減できます。

低スペックの環境では、このオプションを有効にすることを推奨します。", "description": "Describes the purpose of the 'Ignore generic cosmetic filters' feature." }, "3pListsOfBlockedHostsHeader": { @@ -511,6 +511,10 @@ "message": "バックグラウンド通信", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "現在のタブ", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "絞り込み", "description": "English: filter log entries" diff --git a/src/_locales/ka/messages.json b/src/_locales/ka/messages.json index 8f4765e65377d..522a86169472b 100644 --- a/src/_locales/ka/messages.json +++ b/src/_locales/ka/messages.json @@ -356,11 +356,11 @@ "description": "English: Ads" }, "3pGroupPrivacy": { - "message": "კონფიდენციალურობა", + "message": "პირადულობა", "description": "English: Privacy" }, "3pGroupMalware": { - "message": "მავნე დომეინები", + "message": "მავნე დომენები", "description": "English: Malware domains" }, "3pGroupAnnoyances": { @@ -408,7 +408,7 @@ "description": "English: Import and append" }, "1pExport": { - "message": "გამოტანა", + "message": "შენახვა", "description": "English: Export" }, "1pExportFilename": { @@ -436,7 +436,7 @@ "description": "This will persist temporary rules" }, "rulesEdit": { - "message": "რედაქტირება", + "message": "შეცვლა", "description": "Will enable manual-edit mode (textarea)" }, "rulesEditSave": { @@ -444,7 +444,7 @@ "description": "Will save manually-edited content and exit manual-edit mode" }, "rulesEditDiscard": { - "message": "უკუგდება", + "message": "გაუქმება", "description": "Will discard manually-edited content and exit manual-edit mode" }, "rulesImport": { @@ -508,9 +508,13 @@ "description": "Appears in the logger's tab selector" }, "logBehindTheScene": { - "message": "სცენის უკან", + "message": "ფარული მოთხოვნები", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Current tab", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filter log entries", "description": "English: filter log entries" @@ -632,87 +636,87 @@ "description": "English: The message seen by the user to confirm subscription to a ABP filter list" }, "elapsedOneMinuteAgo": { - "message": "a minute ago", + "message": "ერთი წუთის წინ", "description": "English: a minute ago" }, "elapsedManyMinutesAgo": { - "message": "{{value}} minutes ago", + "message": "{{value}} წუთის წინ", "description": "English: {{value}} minutes ago" }, "elapsedOneHourAgo": { - "message": "an hour ago", + "message": "ერთი საათის წინ", "description": "English: an hour ago" }, "elapsedManyHoursAgo": { - "message": "{{value}} hours ago", + "message": "{{value}} საათის წინ", "description": "English: {{value}} hours ago" }, "elapsedOneDayAgo": { - "message": "a day ago", + "message": "ერთი დღის წინ", "description": "English: a day ago" }, "elapsedManyDaysAgo": { - "message": "{{value}} days ago", + "message": "{{value}} დღის წინ", "description": "English: {{value}} days ago" }, "showDashboardButton": { - "message": "Show Dashboard", + "message": "ხელსაწყოების გვერდის ჩვენება", "description": "Firefox\/Fennec-specific: Show Dashboard" }, "showNetworkLogButton": { - "message": "Show Logger", + "message": "აღრიცხვის ჩვენება", "description": "Firefox\/Fennec-specific: Show Logger" }, "fennecMenuItemBlockingOff": { - "message": "off", + "message": "გამორთული", "description": "Firefox-specific: appears as 'uBlock₀ (off)'" }, "docblockedPrompt1": { - "message": "uBlock Origin has prevented the following page from loading:", + "message": "uBlock Origin-მა შეზღუდა მოცემული გვერდის ჩატვირთვა:", "description": "English: uBlock₀ has prevented the following page from loading:" }, "docblockedPrompt2": { - "message": "Because of the following filter", + "message": "მოცემული ფილტრიდან გამომდინარე", "description": "English: Because of the following filter" }, "docblockedNoParamsPrompt": { - "message": "without parameters", + "message": "პარამეტრების გარეშე", "description": "label to be used for the parameter-less URL: https:\/\/cloud.githubusercontent.com\/assets\/585534\/9832014\/bfb1b8f0-593b-11e5-8a27-fba472a5529a.png" }, "docblockedFoundIn": { - "message": "Found in:", + "message": "პოვნა:", "description": "English: List of filter list names follows" }, "docblockedBack": { - "message": "Go back", + "message": "უკან დაბრუნება", "description": "English: Go back" }, "docblockedClose": { - "message": "Close this window", + "message": "ფანჯრის დახურვა", "description": "English: Close this window" }, "docblockedProceed": { - "message": "Disable strict blocking for {{hostname}}", + "message": "მკაცრი შეზღუდვის მოხსნა საიტისთვის: {{hostname}}", "description": "English: Disable strict blocking for {{hostname}} ..." }, "docblockedDisableTemporary": { - "message": "Temporarily", + "message": "დროებით", "description": "English: Temporarily" }, "docblockedDisablePermanent": { - "message": "Permanently", + "message": "მუდმივად", "description": "English: Permanently" }, "cloudPush": { - "message": "Export to cloud storage", + "message": "ღრუბლოვან საცავში შენახვა", "description": "tooltip" }, "cloudPull": { - "message": "Import from cloud storage", + "message": "ღრუბლოვანი საცავიდან გადმოტანა", "description": "tooltip" }, "cloudPullAndMerge": { - "message": "Import from cloud storage and merge with current settings", + "message": "ღრუბლოვანი საცავიდან გადმოტანა და არსებულ პარამეტრებთან მისადაგება", "description": "tooltip" }, "cloudNoData": { @@ -720,31 +724,31 @@ "description": "" }, "cloudDeviceNamePrompt": { - "message": "This device name:", + "message": "ამ მოწყობილობის დასახელება:", "description": "used as a prompt for the user to provide a custom device name" }, "advancedSettingsWarning": { - "message": "Warning! Change these advanced settings at your own risk.", + "message": "გაფრთხილება! დამატებითი პარამეტრებს ცვლილების შედეგებზე, თავად ხართ პასუხისმგებელი.", "description": "A warning to users at the top of 'Advanced settings' page" }, "genericSubmit": { - "message": "Submit", + "message": "მიღება", "description": "for generic 'Submit' buttons" }, "genericApplyChanges": { - "message": "Apply changes", + "message": "ცვლილებების მისადაგება", "description": "for generic 'Apply changes' buttons" }, "genericRevert": { - "message": "Revert", + "message": "დაბრუნება", "description": "for generic 'Revert' buttons" }, "genericBytes": { - "message": "bytes", + "message": "ბაიტი", "description": "" }, "contextMenuTemporarilyAllowLargeMediaElements": { - "message": "Temporarily allow large media elements", + "message": "დიდი მედია-ელემენტების დროებით დაშვება", "description": "A context menu entry, present when large media elements have been blocked on the current site" }, "dummy": { diff --git a/src/_locales/kk/messages.json b/src/_locales/kk/messages.json new file mode 100644 index 0000000000000..5a3f91d20c621 --- /dev/null +++ b/src/_locales/kk/messages.json @@ -0,0 +1,758 @@ +{ + "extName": { + "message": "uBlock₀", + "description": "extension name." + }, + "extShortDesc": { + "message": "Finally, an efficient blocker. Easy on CPU and memory.", + "description": "this will be in the chrome web store: must be 132 characters or less" + }, + "dashboardName": { + "message": "uBlock₀ — Dashboard", + "description": "English: uBlock₀ — Dashboard" + }, + "settingsPageName": { + "message": "Баптаулар", + "description": "appears as tab name in dashboard" + }, + "3pPageName": { + "message": "3-ші жақты сүзгілер", + "description": "appears as tab name in dashboard" + }, + "1pPageName": { + "message": "Менің сүзгілерім", + "description": "appears as tab name in dashboard" + }, + "rulesPageName": { + "message": "Менің ережелерім", + "description": "appears as tab name in dashboard" + }, + "whitelistPageName": { + "message": "Рұқсат тізімі", + "description": "appears as tab name in dashboard" + }, + "statsPageName": { + "message": "uBlock₀ — Logger", + "description": "Title for the logger window" + }, + "aboutPageName": { + "message": "Осы туралы", + "description": "appears as tab name in dashboard" + }, + "advancedSettingsPageName": { + "message": "Кеңейтілген баптаулар", + "description": "Title for the advanced settings page" + }, + "popupPowerSwitchInfo": { + "message": "Click: disable\/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page.", + "description": "English: Click: disable\/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page." + }, + "popupPowerSwitchInfo1": { + "message": "Click to disable uBlock₀ for this site.\n\nCtrl+click to disable uBlock₀ only on this page.", + "description": "Message to be read by screen readers" + }, + "popupPowerSwitchInfo2": { + "message": "Click to enable uBlock₀ for this site.", + "description": "Message to be read by screen readers" + }, + "popupBlockedRequestPrompt": { + "message": "сұраным блокталды", + "description": "English: requests blocked" + }, + "popupBlockedOnThisPagePrompt": { + "message": "бұл парақта", + "description": "English: on this page" + }, + "popupBlockedStats": { + "message": "{{count}} немесе {{percent}}%", + "description": "Example: 15 or 13%" + }, + "popupBlockedSinceInstallPrompt": { + "message": "орнатылғаннан бастап", + "description": "English: since install" + }, + "popupOr": { + "message": "немесе", + "description": "English: or" + }, + "popupTipDashboard": { + "message": "Open the dashboard", + "description": "English: Click to open the dashboard" + }, + "popupTipZapper": { + "message": "Enter element zapper mode", + "description": "Tooltip for the element-zapper icon in the popup panel" + }, + "popupTipPicker": { + "message": "Enter element picker mode", + "description": "English: Enter element picker mode" + }, + "popupTipLog": { + "message": "Open the logger", + "description": "Tooltip used for the logger icon in the panel" + }, + "popupTipNoPopups": { + "message": "Toggle the blocking of all popups for this site", + "description": "Tooltip for the no-popups per-site switch" + }, + "popupTipNoPopups1": { + "message": "Click to block all popups on this site", + "description": "Tooltip for the no-popups per-site switch" + }, + "popupTipNoPopups2": { + "message": "Click to no longer block all popups on this site", + "description": "Tooltip for the no-popups per-site switch" + }, + "popupTipNoLargeMedia": { + "message": "Toggle the blocking of large media elements for this site", + "description": "Tooltip for the no-large-media per-site switch" + }, + "popupTipNoLargeMedia1": { + "message": "Click to block large media elements on this site", + "description": "Tooltip for the no-large-media per-site switch" + }, + "popupTipNoLargeMedia2": { + "message": "Click to no longer block large media elements on this site", + "description": "Tooltip for the no-large-media per-site switch" + }, + "popupTipNoCosmeticFiltering": { + "message": "Toggle cosmetic filtering for this site", + "description": "Tooltip for the no-cosmetic-filtering per-site switch" + }, + "popupTipNoCosmeticFiltering1": { + "message": "Click to disable cosmetic filtering on this site", + "description": "Tooltip for the no-cosmetic-filtering per-site switch" + }, + "popupTipNoCosmeticFiltering2": { + "message": "Click to enable cosmetic filtering on this site", + "description": "Tooltip for the no-cosmetic-filtering per-site switch" + }, + "popupTipNoRemoteFonts": { + "message": "Toggle the blocking of remote fonts for this site", + "description": "Tooltip for the no-remote-fonts per-site switch" + }, + "popupTipNoRemoteFonts1": { + "message": "Click to block remote fonts on this site", + "description": "Tooltip for the no-remote-fonts per-site switch" + }, + "popupTipNoRemoteFonts2": { + "message": "Click to no longer block remote fonts on this site", + "description": "Tooltip for the no-remote-fonts per-site switch" + }, + "popupTipGlobalRules": { + "message": "Global rules: this column is for rules which apply to all sites.", + "description": "Tooltip when hovering the top-most cell of the global-rules column." + }, + "popupTipLocalRules": { + "message": "Local rules: this column is for rules which apply to the current site only.\nLocal rules override global rules.", + "description": "Tooltip when hovering the top-most cell of the local-rules column." + }, + "popupTipSaveRules": { + "message": "Click to make your changes permanent.", + "description": "Tooltip when hovering over the padlock in the dynamic filtering pane." + }, + "popupTipRevertRules": { + "message": "Click to revert your changes.", + "description": "Tooltip when hovering over the eraser in the dynamic filtering pane." + }, + "popupAnyRulePrompt": { + "message": "барлығы", + "description": "" + }, + "popupImageRulePrompt": { + "message": "суреттер", + "description": "" + }, + "popup3pAnyRulePrompt": { + "message": "3-ші жақты", + "description": "" + }, + "popup3pPassiveRulePrompt": { + "message": "3-ші жақты css\/суреттер", + "description": "" + }, + "popupInlineScriptRulePrompt": { + "message": "inline scripts", + "description": "" + }, + "popup1pScriptRulePrompt": { + "message": "1st-party scripts", + "description": "" + }, + "popup3pScriptRulePrompt": { + "message": "3rd-party scripts", + "description": "" + }, + "popup3pFrameRulePrompt": { + "message": "3rd-party frames", + "description": "" + }, + "popupHitDomainCountPrompt": { + "message": "domains connected", + "description": "appears in popup" + }, + "popupHitDomainCount": { + "message": "{{count}} out of {{total}}", + "description": "appears in popup" + }, + "pickerCreate": { + "message": "Жасау", + "description": "English: Create" + }, + "pickerPick": { + "message": "Таңдау", + "description": "English: Pick" + }, + "pickerQuit": { + "message": "Шығу", + "description": "English: Quit" + }, + "pickerPreview": { + "message": "Алдын-ала қарау", + "description": "Element picker preview mode: will cause the elements matching the current filter to be removed from the page" + }, + "pickerNetFilters": { + "message": "Network filters", + "description": "English: header for a type of filter in the element picker dialog" + }, + "pickerCosmeticFilters": { + "message": "Cosmetic filters", + "description": "English: Cosmetic filters" + }, + "pickerCosmeticFiltersHint": { + "message": "Шерту, Ctrl-шерту", + "description": "English: Click, Ctrl-click" + }, + "pickerContextMenuEntry": { + "message": "Элементті блоктау", + "description": "English: Block element" + }, + "settingsCollapseBlockedPrompt": { + "message": "Hide placeholders of blocked elements", + "description": "English: Hide placeholders of blocked elements" + }, + "settingsIconBadgePrompt": { + "message": "Show the number of blocked requests on the icon", + "description": "English: Show the number of blocked requests on the icon" + }, + "settingsTooltipsPrompt": { + "message": "Disable tooltips", + "description": "A checkbox in the Settings pane" + }, + "settingsContextMenuPrompt": { + "message": "Make use of context menu where appropriate", + "description": "English: Make use of context menu where appropriate" + }, + "settingsColorBlindPrompt": { + "message": "Color-blind friendly", + "description": "English: Color-blind friendly" + }, + "settingsCloudStorageEnabledPrompt": { + "message": "Enable cloud storage support", + "description": "" + }, + "settingsAdvancedUserPrompt": { + "message": "I am an advanced user (required reading<\/a>)", + "description": "" + }, + "settingsAdvancedUserSettings": { + "message": "advanced settings", + "description": "For the tooltip of a link which gives access to advanced settings" + }, + "settingsPrefetchingDisabledPrompt": { + "message": "Disable pre-fetching (to prevent any connection for blocked network requests)", + "description": "English: " + }, + "settingsHyperlinkAuditingDisabledPrompt": { + "message": "Disable hyperlink auditing", + "description": "English: " + }, + "settingsWebRTCIPAddressHiddenPrompt": { + "message": "Prevent WebRTC from leaking local IP addresses", + "description": "English: " + }, + "settingPerSiteSwitchGroup": { + "message": "Default behavior", + "description": "" + }, + "settingPerSiteSwitchGroupSynopsis": { + "message": "These default behaviors can be overridden on a per-site basis", + "description": "" + }, + "settingsNoCosmeticFilteringPrompt": { + "message": "Disable cosmetic filtering", + "description": "" + }, + "settingsNoLargeMediaPrompt": { + "message": "Block media elements larger than {{input:number}} kB", + "description": "" + }, + "settingsNoRemoteFontsPrompt": { + "message": "Block remote fonts", + "description": "" + }, + "settingsNoCSPReportsPrompt": { + "message": "Block CSP reports", + "description": "background information: https:\/\/github.com\/gorhill\/uBlock\/issues\/3150" + }, + "settingsStorageUsed": { + "message": "Storage used: {{value}} bytes", + "description": "English: Storage used: {{}} bytes" + }, + "settingsLastRestorePrompt": { + "message": "Last restore:", + "description": "English: Last restore:" + }, + "settingsLastBackupPrompt": { + "message": "Last backup:", + "description": "English: Last backup:" + }, + "3pListsOfBlockedHostsPrompt": { + "message": "{{netFilterCount}} network filters + {{cosmeticFilterCount}} cosmetic filters from:", + "description": "Appears at the top of the _3rd-party filters_ pane" + }, + "3pListsOfBlockedHostsPerListStats": { + "message": "{{used}} used out of {{total}}", + "description": "Appears aside each filter list in the _3rd-party filters_ pane" + }, + "3pAutoUpdatePrompt1": { + "message": "Auto-update filter lists", + "description": "A checkbox in the _3rd-party filters_ pane" + }, + "3pUpdateNow": { + "message": "Update now", + "description": "A button in the in the _3rd-party filters_ pane" + }, + "3pPurgeAll": { + "message": "Purge all caches", + "description": "A button in the in the _3rd-party filters_ pane" + }, + "3pParseAllABPHideFiltersPrompt1": { + "message": "Parse and enforce cosmetic filters", + "description": "English: Parse and enforce Adblock+ element hiding filters." + }, + "3pParseAllABPHideFiltersInfo": { + "message": "

This option enables the parsing and enforcing of Adblock Plus-compatible “element hiding” filters<\/a>. These filters are essentially cosmetic, they serve to hide elements in a web page which are deemed to be a visual nuisance, and which can't be blocked by the net request-based filtering engine.<\/p>

Enabling this feature increases uBlock₀'s memory footprint.<\/p>", + "description": "Describes the purpose of the 'Parse and enforce cosmetic filters' feature." + }, + "3pIgnoreGenericCosmeticFilters": { + "message": "Ignore generic cosmetic filters", + "description": "This will cause uBO to ignore all generic cosmetic filters." + }, + "3pIgnoreGenericCosmeticFiltersInfo": { + "message": "

Generic cosmetic filters are those cosmetic filters which are meant to apply on all web sites.

Though handled efficiently by uBlock₀, generic cosmetic filters may still contribute measurable memory and CPU overhead on some web pages, especially for large and long-lived ones.

Enabling this option will eliminate the memory and CPU overhead added to web pages as a result of handling generic cosmetic filters, and also lower the memory footprint of uBlock₀ itself.

It is recommended to enable this option on less powerful devices.", + "description": "Describes the purpose of the 'Ignore generic cosmetic filters' feature." + }, + "3pListsOfBlockedHostsHeader": { + "message": "Lists of blocked hosts", + "description": "English: Lists of blocked hosts" + }, + "3pApplyChanges": { + "message": "Өзерістерді іске асыру", + "description": "English: Apply changes" + }, + "3pGroupAds": { + "message": "Жарнамалар", + "description": "English: Ads" + }, + "3pGroupPrivacy": { + "message": "Жекелік", + "description": "English: Privacy" + }, + "3pGroupMalware": { + "message": "Malware domains", + "description": "English: Malware domains" + }, + "3pGroupAnnoyances": { + "message": "Annoyances", + "description": "The header identifying the filter lists in the category 'annoyances'" + }, + "3pGroupMultipurpose": { + "message": "Multipurpose", + "description": "English: Multipurpose" + }, + "3pGroupRegions": { + "message": "Regions, languages", + "description": "English: Regions, languages" + }, + "3pGroupCustom": { + "message": "Custom", + "description": "English: Custom" + }, + "3pExternalListsHint": { + "message": "One URL per line. Lines prefixed with ‘!’ will be ignored. Invalid URLs will be silently ignored.", + "description": "English: One URL per line. Lines prefixed with ‘!’ will be ignored. Invalid URLs will be silently ignored." + }, + "3pExternalListObsolete": { + "message": "Out of date.", + "description": "used as a tooltip for the out-of-date icon beside a list" + }, + "3pLastUpdate": { + "message": "Last update: {{ago}}.\nClick to force an update.", + "description": "used as a tooltip for the clock icon beside a list" + }, + "3pUpdating": { + "message": "Updating...", + "description": "used as a tooltip for the spinner icon beside a list" + }, + "3pNetworkError": { + "message": "A network error prevented the resource from being updated.", + "description": "used as a tooltip for error icon beside a list" + }, + "1pFormatHint": { + "message": "One filter per line. A filter can be a plain hostname, or an Adblock Plus-compatible filter. Lines prefixed with ‘!’ will be ignored.", + "description": "English: One filter per line. A filter can be a plain hostname, or an Adblock Plus-compatible filter. Lines prefixed with ‘!’ will be ignored." + }, + "1pImport": { + "message": "Import and append", + "description": "English: Import and append" + }, + "1pExport": { + "message": "Export", + "description": "English: Export" + }, + "1pExportFilename": { + "message": "my-ublock-static-filters_{{datetime}}.txt", + "description": "English: my-ublock-static-filters_{{datetime}}.txt" + }, + "1pApplyChanges": { + "message": "Apply changes", + "description": "English: Apply changes" + }, + "rulesPermanentHeader": { + "message": "Permanent rules", + "description": "header" + }, + "rulesTemporaryHeader": { + "message": "Temporary rules", + "description": "header" + }, + "rulesRevert": { + "message": "Қайтару", + "description": "This will remove all temporary rules" + }, + "rulesCommit": { + "message": "Commit", + "description": "This will persist temporary rules" + }, + "rulesEdit": { + "message": "Түзету", + "description": "Will enable manual-edit mode (textarea)" + }, + "rulesEditSave": { + "message": "Сақтау", + "description": "Will save manually-edited content and exit manual-edit mode" + }, + "rulesEditDiscard": { + "message": "Елемеу", + "description": "Will discard manually-edited content and exit manual-edit mode" + }, + "rulesImport": { + "message": "Файлдан импорттау...", + "description": "" + }, + "rulesExport": { + "message": "Файлға экспорттау", + "description": "" + }, + "rulesDefaultFileName": { + "message": "my-ublock-dynamic-rules_{{datetime}}.txt", + "description": "default file name to use" + }, + "rulesHint": { + "message": "List of your dynamic filtering rules.", + "description": "English: List of your dynamic filtering rules." + }, + "rulesFormatHint": { + "message": "Rule syntax: source destination type action<\/code> (full documentation<\/a>).", + "description": "English: dynamic rule syntax and full documentation." + }, + "whitelistPrompt": { + "message": "The whitelist directives dictate on which web pages uBlock Origin should be disabled. One entry per line. Invalid directives will be silently ignored and commented out.", + "description": "English: An overview of the content of the dashboard's Whitelist pane." + }, + "whitelistImport": { + "message": "Import and append", + "description": "English: Import and append" + }, + "whitelistExport": { + "message": "Экспорт", + "description": "English: Export" + }, + "whitelistExportFilename": { + "message": "my-ublock-whitelist_{{datetime}}.txt", + "description": "English: my-ublock-whitelist_{{datetime}}.txt" + }, + "whitelistApply": { + "message": "Өзерістерді іске асыру", + "description": "English: Apply changes" + }, + "logRequestsHeaderType": { + "message": "Түрі", + "description": "English: Type" + }, + "logRequestsHeaderDomain": { + "message": "Домен", + "description": "English: Domain" + }, + "logRequestsHeaderURL": { + "message": "URL", + "description": "English: URL" + }, + "logRequestsHeaderFilter": { + "message": "Сүзгі", + "description": "English: Filter" + }, + "logAll": { + "message": "Барлығы", + "description": "Appears in the logger's tab selector" + }, + "logBehindTheScene": { + "message": "Behind the scene", + "description": "Pretty name for behind-the-scene network requests" + }, + "loggerCurrentTab": { + "message": "Current tab", + "description": "Appears in the logger's tab selector" + }, + "logFilterPrompt": { + "message": "filter log entries", + "description": "English: filter log entries" + }, + "logMaxEntriesTip": { + "message": "Maximum number of log entries", + "description": "Tooltip informaing that the input field is to set the maximum number of entries in the log" + }, + "loggerURLFilteringContextLabel": { + "message": "Context:", + "description": "Label for the context selector" + }, + "loggerURLFilteringTypeLabel": { + "message": "Түрі:", + "description": "Label for the type selector" + }, + "loggerURLFilteringHeader": { + "message": "Dynamic URL filtering", + "description": "Small header to identify the dynamic URL filtering section" + }, + "loggerStaticFilteringHeader": { + "message": "Static filtering", + "description": "Small header to identify the static filtering section" + }, + "loggerStaticFilteringSentence": { + "message": "{{action}} network requests of {{type}} {{br}}which URL address matches {{url}} {{br}}and which originates {{origin}},{{br}}{{importance}} there is a matching exception filter.", + "description": "Used in the static filtering wizard" + }, + "loggerStaticFilteringSentencePartBlock": { + "message": "Block", + "description": "Used in the static filtering wizard" + }, + "loggerStaticFilteringSentencePartAllow": { + "message": "Allow", + "description": "Used in the static filtering wizard" + }, + "loggerStaticFilteringSentencePartType": { + "message": "type “{{type}}”", + "description": "Used in the static filtering wizard" + }, + "loggerStaticFilteringSentencePartAnyType": { + "message": "any type", + "description": "Used in the static filtering wizard" + }, + "loggerStaticFilteringSentencePartOrigin": { + "message": "from “{{origin}}”", + "description": "Used in the static filtering wizard" + }, + "loggerStaticFilteringSentencePartAnyOrigin": { + "message": "from anywhere", + "description": "Used in the static filtering wizard" + }, + "loggerStaticFilteringSentencePartNotImportant": { + "message": "except when", + "description": "Used in the static filtering wizard" + }, + "loggerStaticFilteringSentencePartImportant": { + "message": "even if", + "description": "Used in the static filtering wizard" + }, + "loggerStaticFilteringFinderSentence1": { + "message": "Static filter {{filter}} found in:", + "description": "Below this sentence, the filter lists in which the filter was found" + }, + "aboutChangelog": { + "message": "Change log", + "description": "English: Change log" + }, + "aboutWiki": { + "message": "Wiki", + "description": "English: project' wiki on Github" + }, + "aboutSupport": { + "message": "Support", + "description": "A link for where to get support" + }, + "aboutCode": { + "message": "Source code (GPLv3)", + "description": "English: Source code (GPLv3)" + }, + "aboutContributors": { + "message": "Contributors", + "description": "English: Contributors" + }, + "aboutBackupDataButton": { + "message": "Back up to file", + "description": "Text for button to create a backup of all settings" + }, + "aboutBackupFilename": { + "message": "my-ublock-backup_{{datetime}}.txt", + "description": "English: my-ublock-backup_{{datetime}}.txt" + }, + "aboutRestoreDataButton": { + "message": "Restore from file...", + "description": "English: Restore from file..." + }, + "aboutResetDataButton": { + "message": "Reset to default settings...", + "description": "English: Reset to default settings..." + }, + "aboutRestoreDataConfirm": { + "message": "All your settings will be overwritten using data backed up on {{time}}, and uBlock₀ will restart.\n\nOverwrite all existing settings using backed up data?", + "description": "Message asking user to confirm restore" + }, + "aboutRestoreDataError": { + "message": "The data could not be read or is invalid", + "description": "Message to display when an error occurred during restore" + }, + "aboutResetDataConfirm": { + "message": "All your settings will be removed, and uBlock₀ will restart.\n\nReset uBlock₀ to factory settings?", + "description": "Message asking user to confirm reset" + }, + "errorCantConnectTo": { + "message": "Network error: {{msg}}", + "description": "English: Network error: {{msg}}" + }, + "subscriberConfirm": { + "message": "uBlock₀: Add the following URL to your custom filter lists?\n\nTitle: \"{{title}}\"\nURL: {{url}}", + "description": "English: The message seen by the user to confirm subscription to a ABP filter list" + }, + "elapsedOneMinuteAgo": { + "message": "a minute ago", + "description": "English: a minute ago" + }, + "elapsedManyMinutesAgo": { + "message": "{{value}} minutes ago", + "description": "English: {{value}} minutes ago" + }, + "elapsedOneHourAgo": { + "message": "an hour ago", + "description": "English: an hour ago" + }, + "elapsedManyHoursAgo": { + "message": "{{value}} hours ago", + "description": "English: {{value}} hours ago" + }, + "elapsedOneDayAgo": { + "message": "a day ago", + "description": "English: a day ago" + }, + "elapsedManyDaysAgo": { + "message": "{{value}} days ago", + "description": "English: {{value}} days ago" + }, + "showDashboardButton": { + "message": "Show Dashboard", + "description": "Firefox\/Fennec-specific: Show Dashboard" + }, + "showNetworkLogButton": { + "message": "Show Logger", + "description": "Firefox\/Fennec-specific: Show Logger" + }, + "fennecMenuItemBlockingOff": { + "message": "off", + "description": "Firefox-specific: appears as 'uBlock₀ (off)'" + }, + "docblockedPrompt1": { + "message": "uBlock Origin has prevented the following page from loading:", + "description": "English: uBlock₀ has prevented the following page from loading:" + }, + "docblockedPrompt2": { + "message": "Because of the following filter", + "description": "English: Because of the following filter" + }, + "docblockedNoParamsPrompt": { + "message": "without parameters", + "description": "label to be used for the parameter-less URL: https:\/\/cloud.githubusercontent.com\/assets\/585534\/9832014\/bfb1b8f0-593b-11e5-8a27-fba472a5529a.png" + }, + "docblockedFoundIn": { + "message": "Found in:", + "description": "English: List of filter list names follows" + }, + "docblockedBack": { + "message": "Go back", + "description": "English: Go back" + }, + "docblockedClose": { + "message": "Close this window", + "description": "English: Close this window" + }, + "docblockedProceed": { + "message": "Disable strict blocking for {{hostname}}", + "description": "English: Disable strict blocking for {{hostname}} ..." + }, + "docblockedDisableTemporary": { + "message": "Temporarily", + "description": "English: Temporarily" + }, + "docblockedDisablePermanent": { + "message": "Permanently", + "description": "English: Permanently" + }, + "cloudPush": { + "message": "Export to cloud storage", + "description": "tooltip" + }, + "cloudPull": { + "message": "Import from cloud storage", + "description": "tooltip" + }, + "cloudPullAndMerge": { + "message": "Import from cloud storage and merge with current settings", + "description": "tooltip" + }, + "cloudNoData": { + "message": "...\n...", + "description": "" + }, + "cloudDeviceNamePrompt": { + "message": "This device name:", + "description": "used as a prompt for the user to provide a custom device name" + }, + "advancedSettingsWarning": { + "message": "Warning! Change these advanced settings at your own risk.", + "description": "A warning to users at the top of 'Advanced settings' page" + }, + "genericSubmit": { + "message": "Submit", + "description": "for generic 'Submit' buttons" + }, + "genericApplyChanges": { + "message": "Apply changes", + "description": "for generic 'Apply changes' buttons" + }, + "genericRevert": { + "message": "Revert", + "description": "for generic 'Revert' buttons" + }, + "genericBytes": { + "message": "bytes", + "description": "" + }, + "contextMenuTemporarilyAllowLargeMediaElements": { + "message": "Temporarily allow large media elements", + "description": "A context menu entry, present when large media elements have been blocked on the current site" + }, + "dummy": { + "message": "This entry must be the last one", + "description": "so we dont need to deal with comma for last entry" + } +} \ No newline at end of file diff --git a/src/_locales/kn/messages.json b/src/_locales/kn/messages.json index f68f732d35d39..8e6a240f98ca8 100644 --- a/src/_locales/kn/messages.json +++ b/src/_locales/kn/messages.json @@ -511,6 +511,10 @@ "message": "Behind the scene", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Current tab", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filter log entries", "description": "English: filter log entries" diff --git a/src/_locales/ko/messages.json b/src/_locales/ko/messages.json index 8e71b6b0a22c5..d66723336497e 100644 --- a/src/_locales/ko/messages.json +++ b/src/_locales/ko/messages.json @@ -48,7 +48,7 @@ "description": "English: Click: disable\/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page." }, "popupPowerSwitchInfo1": { - "message": "클릭: 이 사이트에서 uBlock₀을 끕니다.\n\nCtrl+클릭: 이 페이지에서 uBlock₀을 끕니다.", + "message": "클릭: 이 사이트에서 uBlock₀을 끕니다.\n\nCtrl+클릭: 이 페이지에서만 uBlock₀을 끕니다.", "description": "Message to be read by screen readers" }, "popupPowerSwitchInfo2": { @@ -364,7 +364,7 @@ "description": "English: Malware domains" }, "3pGroupAnnoyances": { - "message": "성가신 것", + "message": "골칫거리", "description": "The header identifying the filter lists in the category 'annoyances'" }, "3pGroupMultipurpose": { @@ -511,6 +511,10 @@ "message": "숨겨진 구성 요소", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "현재 탭", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "필터 로그 항목", "description": "English: filter log entries" diff --git a/src/_locales/lt/messages.json b/src/_locales/lt/messages.json index fdeda38d1c542..6ba10cabb9cbd 100644 --- a/src/_locales/lt/messages.json +++ b/src/_locales/lt/messages.json @@ -511,6 +511,10 @@ "message": "Užkulisiai", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Dabartinė kortelė", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtruoti žurnalo įrašus", "description": "English: filter log entries" diff --git a/src/_locales/lv/messages.json b/src/_locales/lv/messages.json index 06e3b91bf807d..bb25920d54811 100644 --- a/src/_locales/lv/messages.json +++ b/src/_locales/lv/messages.json @@ -511,6 +511,10 @@ "message": "Aizkulisēs", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Current tab", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "žurnāla ierakstu filtrs", "description": "English: filter log entries" diff --git a/src/_locales/ml/messages.json b/src/_locales/ml/messages.json index 2ecb4877067a4..ffdb6c36aaba5 100644 --- a/src/_locales/ml/messages.json +++ b/src/_locales/ml/messages.json @@ -511,6 +511,10 @@ "message": "സീനിനു പിന്നില്‍", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Current tab", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "ലോഗ് എന്‍ട്രി ഫില്‍ട്ടര്‍ ചെയ്യുക", "description": "English: filter log entries" diff --git a/src/_locales/mr/messages.json b/src/_locales/mr/messages.json index 16b43a6d6653b..1e5d11b5b32d4 100644 --- a/src/_locales/mr/messages.json +++ b/src/_locales/mr/messages.json @@ -511,6 +511,10 @@ "message": "Behind the scene", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Current tab", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filter log entries", "description": "English: filter log entries" diff --git a/src/_locales/ms/messages.json b/src/_locales/ms/messages.json index 900670bcf5535..ae44caa169fd3 100644 --- a/src/_locales/ms/messages.json +++ b/src/_locales/ms/messages.json @@ -511,6 +511,10 @@ "message": "Behind the scene", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Current tab", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filter log entries", "description": "English: filter log entries" diff --git a/src/_locales/nb/messages.json b/src/_locales/nb/messages.json index 75de1f938b5e3..364bc3df86e6f 100644 --- a/src/_locales/nb/messages.json +++ b/src/_locales/nb/messages.json @@ -344,7 +344,7 @@ "description": "Describes the purpose of the 'Ignore generic cosmetic filters' feature." }, "3pListsOfBlockedHostsHeader": { - "message": "Lister over blokkerte verter", + "message": "Lists of blocked hosts", "description": "English: Lists of blocked hosts" }, "3pApplyChanges": { @@ -511,6 +511,10 @@ "message": "Bak kulissene", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Gjeldende fane", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtrer loggoppføringer", "description": "English: filter log entries" diff --git a/src/_locales/nl/messages.json b/src/_locales/nl/messages.json index 3804f7b674c00..e6cc15201721c 100644 --- a/src/_locales/nl/messages.json +++ b/src/_locales/nl/messages.json @@ -511,6 +511,10 @@ "message": "Achter de schermen", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Huidige tabblad", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "logboekvermeldingen filteren", "description": "English: filter log entries" diff --git a/src/_locales/pl/messages.json b/src/_locales/pl/messages.json index e17038f4b5d41..572f986aad40b 100644 --- a/src/_locales/pl/messages.json +++ b/src/_locales/pl/messages.json @@ -4,7 +4,7 @@ "description": "extension name." }, "extShortDesc": { - "message": "Nareszcie, skuteczny bloker, charakteryzujący się niskim użyciem procesora i pamięci.", + "message": "Nareszcie skuteczny bloker charakteryzujący się niskim użyciem procesora i pamięci.", "description": "this will be in the chrome web store: must be 132 characters or less" }, "dashboardName": { @@ -511,6 +511,10 @@ "message": "Ukryte żądania", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Aktywna karta", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtruj wpisy", "description": "English: filter log entries" diff --git a/src/_locales/pt_BR/messages.json b/src/_locales/pt_BR/messages.json index 1bb458ace4dce..ff2c5a6a2c18f 100644 --- a/src/_locales/pt_BR/messages.json +++ b/src/_locales/pt_BR/messages.json @@ -432,7 +432,7 @@ "description": "This will remove all temporary rules" }, "rulesCommit": { - "message": "Confirmar", + "message": "Aplicar", "description": "This will persist temporary rules" }, "rulesEdit": { @@ -511,6 +511,10 @@ "message": "Por trás da cena", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Aba atual", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtrar entradas de registro", "description": "English: filter log entries" diff --git a/src/_locales/pt_PT/messages.json b/src/_locales/pt_PT/messages.json index e726a731308a6..d447bfc2236b0 100644 --- a/src/_locales/pt_PT/messages.json +++ b/src/_locales/pt_PT/messages.json @@ -511,6 +511,10 @@ "message": "Bastidores", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Separador atual", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtrar entradas de registo", "description": "English: filter log entries" diff --git a/src/_locales/ro/messages.json b/src/_locales/ro/messages.json index ac9d254cece5a..c3537f0816c62 100644 --- a/src/_locales/ro/messages.json +++ b/src/_locales/ro/messages.json @@ -48,11 +48,11 @@ "description": "English: Click: disable\/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page." }, "popupPowerSwitchInfo1": { - "message": "Dați clic pentru a dezactiva uBlock₀ pentru acest sait.\n\nDați Ctrl+clic pentru a dezactiva uBlock₀ doar pe această pagină.", + "message": "Clic ca să dezactivezi uBo pentru acest site.\n\nCtrl+clic ca sa dezactivezi uBo doar pe această pagină.", "description": "Message to be read by screen readers" }, "popupPowerSwitchInfo2": { - "message": "Dați clic pentru a activa uBlock₀ pentru acest sait.", + "message": "Dați clic pentru a activa uBlock₀ pentru acest site.", "description": "Message to be read by screen readers" }, "popupBlockedRequestPrompt": { @@ -108,11 +108,11 @@ "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia1": { - "message": "Dați clic pentru a bloca elementele media de mari dimensiuni pentru acest sait", + "message": "Dați clic pentru a bloca elementele media de mari dimensiuni pentru acest site", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia2": { - "message": "Dați clic pentru a nu mai bloca elementele media de mari dimensiuni pentru acest sait", + "message": "Dați clic pentru a nu mai bloca elementele media de mari dimensiuni pentru acest site", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoCosmeticFiltering": { @@ -120,11 +120,11 @@ "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering1": { - "message": "Dați clic pentru a dezactiva filtrele vizuale pentru acest sait", + "message": "Dați clic pentru a dezactiva filtrele vizuale pentru acest site", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering2": { - "message": "Dați clic pentru a activa filtrele vizuale pentru acest sait", + "message": "Dați clic pentru a activa filtrele vizuale pentru acest site", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoRemoteFonts": { @@ -136,7 +136,7 @@ "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts2": { - "message": "Dați clic pentru a nu mai bloca fonturile externe pentru acest sait", + "message": "Dați clic pentru a nu mai bloca fonturile externe pentru acest site", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipGlobalRules": { @@ -511,6 +511,10 @@ "message": "În spatele scenei", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Tab-ul curent", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtrează intrările din jurnal", "description": "English: filter log entries" diff --git a/src/_locales/ru/messages.json b/src/_locales/ru/messages.json index a13072c283fd5..3c926ea169fdf 100644 --- a/src/_locales/ru/messages.json +++ b/src/_locales/ru/messages.json @@ -511,6 +511,10 @@ "message": "Скрытые запросы", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Текущая вкладка", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "фильтр записей в журнале", "description": "English: filter log entries" diff --git a/src/_locales/sk/messages.json b/src/_locales/sk/messages.json index 85036319fb588..4cacc2add524d 100644 --- a/src/_locales/sk/messages.json +++ b/src/_locales/sk/messages.json @@ -511,6 +511,10 @@ "message": "Za oponou", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Aktívna karta", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtrovať položky v zázname", "description": "English: filter log entries" diff --git a/src/_locales/sl/messages.json b/src/_locales/sl/messages.json index 13d30f2baae57..3f224153ef794 100644 --- a/src/_locales/sl/messages.json +++ b/src/_locales/sl/messages.json @@ -48,11 +48,11 @@ "description": "English: Click: disable\/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page." }, "popupPowerSwitchInfo1": { - "message": "Click to disable uBlock₀ for this site.\n\nCtrl+click to disable uBlock₀ only on this page.", + "message": "Kliknite, da onemogočite uBlock₀ za to stran.\n\nPritisnite Ctrl in kliknite, da onemogočite uBlock₀ samo na tej strani.", "description": "Message to be read by screen readers" }, "popupPowerSwitchInfo2": { - "message": "Click to enable uBlock₀ for this site.", + "message": "Kliknite, da omogočite uBlock₀ za to stran.", "description": "Message to be read by screen readers" }, "popupBlockedRequestPrompt": { @@ -96,11 +96,11 @@ "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups1": { - "message": "Click to block all popups on this site", + "message": "Kliknite, da omogičite blokiranje vseh pojavnih oken za to stran", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups2": { - "message": "Click to no longer block all popups on this site", + "message": "Kliknite, da onemogočite blokiranje vsah pojavnih oken na tej strani", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoLargeMedia": { @@ -108,11 +108,11 @@ "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia1": { - "message": "Click to block large media elements on this site", + "message": "Kliknite, da omogičite blokiranje večjih medijev za to stran", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia2": { - "message": "Click to no longer block large media elements on this site", + "message": "Kliknite, da onemogočite blokiranje večjih medijev za to stran", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoCosmeticFiltering": { @@ -120,11 +120,11 @@ "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering1": { - "message": "Click to disable cosmetic filtering on this site", + "message": "Kliknite, da onemogočite kozmetično filtriranje na tej strani", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering2": { - "message": "Click to enable cosmetic filtering on this site", + "message": "Kliknite, da omogočite kozmetično filtriranje na tej strani", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoRemoteFonts": { @@ -132,11 +132,11 @@ "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts1": { - "message": "Click to block remote fonts on this site", + "message": "Kliknite, da omogočite blokiranje oddaljenih pisav na tej strani", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts2": { - "message": "Click to no longer block remote fonts on this site", + "message": "Kliknite, da onemogočite blokiranje oddaljenih pisav na tej strani", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipGlobalRules": { @@ -164,7 +164,7 @@ "description": "" }, "popup3pAnyRulePrompt": { - "message": "tretje-osebne", + "message": "tretjih oseb", "description": "" }, "popup3pPassiveRulePrompt": { @@ -292,7 +292,7 @@ "description": "" }, "settingsNoCSPReportsPrompt": { - "message": "Block CSP reports", + "message": "Blokiraj CSP poročila", "description": "background information: https:\/\/github.com\/gorhill\/uBlock\/issues\/3150" }, "settingsStorageUsed": { @@ -364,7 +364,7 @@ "description": "English: Malware domains" }, "3pGroupAnnoyances": { - "message": "Annoyances", + "message": "\t\nNadlegovanje", "description": "The header identifying the filter lists in the category 'annoyances'" }, "3pGroupMultipurpose": { @@ -511,6 +511,10 @@ "message": "Za zaveso", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Current tab", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtriraj vnose dnevnika", "description": "English: filter log entries" diff --git a/src/_locales/sq/messages.json b/src/_locales/sq/messages.json index 811c799634b20..a2a402a1fa3e9 100644 --- a/src/_locales/sq/messages.json +++ b/src/_locales/sq/messages.json @@ -276,7 +276,7 @@ "description": "" }, "settingPerSiteSwitchGroupSynopsis": { - "message": "Këto vlera bëhen të panevojshme, sipas rastit", + "message": "Këto vlera mund të ndryshohen në bazë të faqeve", "description": "" }, "settingsNoCosmeticFilteringPrompt": { @@ -440,7 +440,7 @@ "description": "Will enable manual-edit mode (textarea)" }, "rulesEditSave": { - "message": "Ruaj", + "message": "Regjistroj", "description": "Will save manually-edited content and exit manual-edit mode" }, "rulesEditDiscard": { @@ -460,7 +460,7 @@ "description": "default file name to use" }, "rulesHint": { - "message": "Lista e rregullave tuaja për filtrimin dinamik.", + "message": "Lista e rregullave për filtrimin dinamik.", "description": "English: List of your dynamic filtering rules." }, "rulesFormatHint": { @@ -511,6 +511,10 @@ "message": "Në prapaskenë", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Skeda aktuale", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtroni elementet në regjistër", "description": "English: filter log entries" @@ -608,11 +612,11 @@ "description": "English: Restore from file..." }, "aboutResetDataButton": { - "message": "Kthej parametrat e mëparshëm...", + "message": "Kthej parametrat fillestarë...", "description": "English: Reset to default settings..." }, "aboutRestoreDataConfirm": { - "message": "Të gjithë parametrat do të mbishkruhen me të dhënat e kopjuara më {{time}}, dhe uBlock₀ do të hapet përsëri.\n\nDo i mbishkruani parametrat aktualë?", + "message": "Të gjithë parametrat do të mbishkruhen me të dhënat e kopjuara më {{time}}, dhe uBlock₀ do të hapet përsëri.\n\nTë mbishkruhen parametrat aktualë?", "description": "Message asking user to confirm restore" }, "aboutRestoreDataError": { @@ -620,7 +624,7 @@ "description": "Message to display when an error occurred during restore" }, "aboutResetDataConfirm": { - "message": "Të gjithë parametrat do të fshihen dhe uBlock₀ do të hapet përsëri.\n\nDo i ktheni parametrat origjinalë?", + "message": "Të gjithë parametrat do të fshihen dhe uBlock₀ do të hapet përsëri.\n\nTë kthehen parametrat origjinalë?", "description": "Message asking user to confirm reset" }, "errorCantConnectTo": { @@ -628,7 +632,7 @@ "description": "English: Network error: {{msg}}" }, "subscriberConfirm": { - "message": "uBlock₀: Do e shtoni këtë adresën në listën e filtrave tuaj?\n\nTitulli: \"{{title}}\"\nURL: {{url}}", + "message": "uBlock₀: Të shtohet adresa në listën e filtrave tuaj?\n\nTitulli: \"{{title}}\"\nURL: {{url}}", "description": "English: The message seen by the user to confirm subscription to a ABP filter list" }, "elapsedOneMinuteAgo": { diff --git a/src/_locales/sr/messages.json b/src/_locales/sr/messages.json index 08add989bab32..04d7021a8acdb 100644 --- a/src/_locales/sr/messages.json +++ b/src/_locales/sr/messages.json @@ -511,6 +511,10 @@ "message": "Иза сцене", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Тренутна картица", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "филтрирај уносе дневника", "description": "English: filter log entries" diff --git a/src/_locales/sv/messages.json b/src/_locales/sv/messages.json index 6861d3929e06a..d0c2e63aa1057 100644 --- a/src/_locales/sv/messages.json +++ b/src/_locales/sv/messages.json @@ -52,7 +52,7 @@ "description": "Message to be read by screen readers" }, "popupPowerSwitchInfo2": { - "message": "Klicka för att aktivera uBlock₀ för den här webbplatsen.", + "message": "Klicka för att aktivera uBlock₀ på den här webbplatsen.", "description": "Message to be read by screen readers" }, "popupBlockedRequestPrompt": { @@ -96,11 +96,11 @@ "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups1": { - "message": "Klicka för att blockera alla popup-fönster på denna sida", + "message": "Klicka för att blockera alla poppupp-fönster på den här webbplatsen", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups2": { - "message": "Klicka för att inte längre blockera alla popup-fönster på den här webbplatsen", + "message": "Klicka för att inte längre blockera alla poppupp-fönster på den här webbplatsen", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoLargeMedia": { @@ -120,11 +120,11 @@ "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering1": { - "message": "Klicka för att inaktivera kosmetiska filter för den här webbplatsen", + "message": "Klicka för att inaktivera kosmetiska filter på den här webbplatsen", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering2": { - "message": "Klicka för att aktivera kosmetisk filtrering på denna sida", + "message": "Klicka för att aktivera kosmetisk filtrering på den här webbplatsen", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoRemoteFonts": { @@ -511,6 +511,10 @@ "message": "Under huven", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Aktuell flik", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "filtrera loggposter", "description": "English: filter log entries" diff --git a/src/_locales/ta/messages.json b/src/_locales/ta/messages.json index f03bda05bd428..317afca55c8ae 100644 --- a/src/_locales/ta/messages.json +++ b/src/_locales/ta/messages.json @@ -511,6 +511,10 @@ "message": "திறக்கப்பட்ட இ.தளங்களின் பின்னால் நடப்பவை", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Current tab", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "பதிகை உருப்படிகளை வடிகட்டு", "description": "English: filter log entries" diff --git a/src/_locales/te/messages.json b/src/_locales/te/messages.json index 2538f75b7be3f..9870d807c69a2 100644 --- a/src/_locales/te/messages.json +++ b/src/_locales/te/messages.json @@ -92,15 +92,15 @@ "description": "Tooltip used for the logger icon in the panel" }, "popupTipNoPopups": { - "message": "ఈ వెబ్సైట్ లో అన్ని పాప్అప్స్ ని నిషేధించు", + "message": "ఈ వెబ్సైట్ లో అన్ని పాప్అప్స్ ని నిషేధించు\/అనుమతించు", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups1": { - "message": "ఇకపై ఈ సైట్లో అన్ని పాపప్లు బ్లాక్ చేయుటకు క్లిక్ చేయండి", + "message": "ఇకపై ఈ సైట్లో అన్ని పపప్లను నిరోధించుటకు క్లిక్ చేయండి", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoPopups2": { - "message": "ఇకపై ఈ సైట్లో అన్ని పాపప్లు బ్లాక్ చేయకుండా ఉండుటకు క్లిక్ చేయండి", + "message": "ఇకపై ఈ సైట్లో అన్ని పపప్లను అనుమతించుటకు క్లిక్ చేయండి", "description": "Tooltip for the no-popups per-site switch" }, "popupTipNoLargeMedia": { @@ -108,11 +108,11 @@ "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia1": { - "message": "ఈ సైట్లో పెద్ద పరిమాణం మీడియా అంశాలు బ్లాక్ చేయుటకు క్లిక్ చేయండి", + "message": "ఈ సైట్లో పెద్ద పరిమాణం మీడియా అంశాలను నిరోధించుటకు క్లిక్ చేయండి", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoLargeMedia2": { - "message": "ఇకపై ఈ సైట్లో పెద్ద పరిమాణం మీడియా అంశాలు బ్లాక్ చేయకుండా ఉండుటకు క్లిక్ చేయండి", + "message": "ఇకపై ఈ సైట్లో పెద్ద పరిమాణం మీడియా అంశాలను అనుమతించుటకు క్లిక్ చేయండి", "description": "Tooltip for the no-large-media per-site switch" }, "popupTipNoCosmeticFiltering": { @@ -120,11 +120,11 @@ "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering1": { - "message": "ఈ వెబ్ సైట్ లో కాస్మెటిక్ ఫిల్టరింగ్ ని అచేతనంచేయుటకు క్లిక్ చేయండి", + "message": "ఈ వెబ్ సైట్ లో కాస్మెటిక్ ఫిల్టరింగ్ ని అచేతనపరచుటకు క్లిక్ చేయండి", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoCosmeticFiltering2": { - "message": "ఈ వెబ్ సైట్ లో కాస్మెటిక్ ఫిల్టరింగ్ ని చేతనంచేయుటకు క్లిక్ చేయండి", + "message": "ఈ వెబ్ సైట్ లో కాస్మెటిక్ ఫిల్టరింగ్ ని చేతపరచుటకు క్లిక్ చేయండి", "description": "Tooltip for the no-cosmetic-filtering per-site switch" }, "popupTipNoRemoteFonts": { @@ -132,11 +132,11 @@ "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts1": { - "message": "ఈ సైట్లో రిమోట్ ఫాంట్లను బ్లాక్ చేయుటకు క్లిక్ చేయండి", + "message": "ఈ సైట్లోని రిమోట్ ఫాంట్లను నిరోధించుటకు క్లిక్ చేయండి", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipNoRemoteFonts2": { - "message": "ఇకపై ఈ సైట్లో రిమోట్ ఫాంట్లను బ్లాక్ చేయకుండా ఉండుటకు క్లిక్ చేయండి", + "message": "ఇకపై ఈ సైట్లో రిమోట్ ఫాంట్లను అనుమతించుటకు క్లిక్ చేయండి", "description": "Tooltip for the no-remote-fonts per-site switch" }, "popupTipGlobalRules": { @@ -152,7 +152,7 @@ "description": "Tooltip when hovering over the padlock in the dynamic filtering pane." }, "popupTipRevertRules": { - "message": "మీ మార్పులను తిరిగరాయుటకు క్లిక్ చేయండి.", + "message": "మీ మార్పులను తిరిగి పూర్వావస్థకు చేర్చుటకు క్లిక్ చేయండి.", "description": "Tooltip when hovering over the eraser in the dynamic filtering pane." }, "popupAnyRulePrompt": { @@ -292,7 +292,7 @@ "description": "" }, "settingsNoCSPReportsPrompt": { - "message": "CSP నివేదికలను బ్లాక్ చేయండి", + "message": "CSP నివేదికలను నిరోధించు", "description": "background information: https:\/\/github.com\/gorhill\/uBlock\/issues\/3150" }, "settingsStorageUsed": { @@ -511,6 +511,10 @@ "message": "తేర వెనుక", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "ప్రస్తుత ట్యాబ్", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "చిట్టాలోని నమోదులను జల్లెడపరచు", "description": "English: filter log entries" diff --git a/src/_locales/tr/messages.json b/src/_locales/tr/messages.json index 8e3bffd636c40..c68fa1c7daf1d 100644 --- a/src/_locales/tr/messages.json +++ b/src/_locales/tr/messages.json @@ -44,7 +44,7 @@ "description": "Title for the advanced settings page" }, "popupPowerSwitchInfo": { - "message": "Tıklama: uBlock₀'i bu site için devre dışı bırak\/etkinleştir.\n\nCtrl+tıklama: uBlock₀'i sadece bu sayfa için devre dışı bırak.", + "message": "Tıklama: uBlock₀'i bu site için devre dışı bırak\/etkinleştir.\n\nCtrl+tıklama: uBlock₀'i yalnızca bu sayfada devre dışı bırak.", "description": "English: Click: disable\/enable uBlock₀ for this site.\n\nCtrl+click: disable uBlock₀ only on this page." }, "popupPowerSwitchInfo1": { @@ -68,7 +68,7 @@ "description": "Example: 15 or 13%" }, "popupBlockedSinceInstallPrompt": { - "message": "yüklendiğinden beri", + "message": "kurulumdan bu yana", "description": "English: since install" }, "popupOr": { @@ -228,11 +228,11 @@ "description": "English: Block element" }, "settingsCollapseBlockedPrompt": { - "message": "Engellenen reklamların yerlerini gizle", + "message": "Engellenmiş ögelerin yertutucularını gizle", "description": "English: Hide placeholders of blocked elements" }, "settingsIconBadgePrompt": { - "message": "Engellenmiş reklam sayısını simge üzerinde göster", + "message": "Engellenen istek sayısını simge üstünde göster", "description": "English: Show the number of blocked requests on the icon" }, "settingsTooltipsPrompt": { @@ -252,7 +252,7 @@ "description": "" }, "settingsAdvancedUserPrompt": { - "message": "Deneyimli kullanıcıyım (Okunması gerekir<\/a>)", + "message": "Deneyimli kullanıcıyım (okunması gerekir<\/a>)", "description": "" }, "settingsAdvancedUserSettings": { @@ -284,7 +284,7 @@ "description": "" }, "settingsNoLargeMediaPrompt": { - "message": "Belirlenenden büyük medya ögelerini engelle {{input:number}} kB", + "message": "{{input:number}} kB'tan büyük medya ögelerini engelle", "description": "" }, "settingsNoRemoteFontsPrompt": { @@ -328,7 +328,7 @@ "description": "A button in the in the _3rd-party filters_ pane" }, "3pParseAllABPHideFiltersPrompt1": { - "message": "Kozmetik süzgeçleri incele ve uygula.", + "message": "Kozmetik süzgeçleri incele ve uygula", "description": "English: Parse and enforce Adblock+ element hiding filters." }, "3pParseAllABPHideFiltersInfo": { @@ -448,7 +448,7 @@ "description": "Will discard manually-edited content and exit manual-edit mode" }, "rulesImport": { - "message": "Dosyadan içe aktar...", + "message": "Dosyadan al...", "description": "" }, "rulesExport": { @@ -468,7 +468,7 @@ "description": "English: dynamic rule syntax and full documentation." }, "whitelistPrompt": { - "message": "Hangi alan adları için uBlock₀'in devre dışı olacağını belirten listeniz. Satır başına bir girdi. Geçersiz alan adları sessizce yok sayılır.", + "message": "Beyaz liste yönergeleri, uBlock₀'in devre dışı bırakılması gerektiği web sayfalarını belirler. Satır başına bir girdi. Geçersiz yönergeler sessizce yok sayılır ve yoruma dönüştürülür.", "description": "English: An overview of the content of the dashboard's Whitelist pane." }, "whitelistImport": { @@ -511,12 +511,16 @@ "message": "Perde arkası", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Geçerli sekme", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { - "message": "günlük girişlerini süz", + "message": "günlük girdilerini süz", "description": "English: filter log entries" }, "logMaxEntriesTip": { - "message": "Maksimum günlük giriş sayısı", + "message": "En fazla günlük girdi sayısı", "description": "Tooltip informaing that the input field is to set the maximum number of entries in the log" }, "loggerURLFilteringContextLabel": { @@ -620,7 +624,7 @@ "description": "Message to display when an error occurred during restore" }, "aboutResetDataConfirm": { - "message": "Tüm ayarlarınızı silinecek, ve uBlock₀ yeniden başlayacak.\n\nuBlock₀ fabrika ayarlarına geri dönsün mü?", + "message": "Tüm ayarlarınızı silinecek, ve uBlock₀ yeniden başlayacak.\n\nuBlock₀ fabrika ayarlarına sıfırlansın mı?", "description": "Message asking user to confirm reset" }, "errorCantConnectTo": { @@ -724,7 +728,7 @@ "description": "used as a prompt for the user to provide a custom device name" }, "advancedSettingsWarning": { - "message": "Uyarı! Bu ayarları değiştirme sorumluluğu size aittir.", + "message": "Uyarı! Bu gelişmiş ayarları değiştirmenin sorumluluğu size aittir.", "description": "A warning to users at the top of 'Advanced settings' page" }, "genericSubmit": { diff --git a/src/_locales/uk/messages.json b/src/_locales/uk/messages.json index f412b4f4856de..253db74238ce8 100644 --- a/src/_locales/uk/messages.json +++ b/src/_locales/uk/messages.json @@ -511,6 +511,10 @@ "message": "За лаштунками", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Поточна вкладка", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "фільтр журналу записів", "description": "English: filter log entries" diff --git a/src/_locales/vi/messages.json b/src/_locales/vi/messages.json index b9707f6604746..bc38e23b85766 100644 --- a/src/_locales/vi/messages.json +++ b/src/_locales/vi/messages.json @@ -511,6 +511,10 @@ "message": "Behind the scene", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "Current tab", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "lọc mục ghi nhận", "description": "English: filter log entries" diff --git a/src/_locales/zh_CN/messages.json b/src/_locales/zh_CN/messages.json index 6204a79418f9c..dee4c5fa3d2bb 100644 --- a/src/_locales/zh_CN/messages.json +++ b/src/_locales/zh_CN/messages.json @@ -511,6 +511,10 @@ "message": "后台", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "当前标签页", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "过滤日志条目", "description": "English: filter log entries" diff --git a/src/_locales/zh_TW/messages.json b/src/_locales/zh_TW/messages.json index 76c8401581979..feeb8dd67474f 100644 --- a/src/_locales/zh_TW/messages.json +++ b/src/_locales/zh_TW/messages.json @@ -511,6 +511,10 @@ "message": "背景的網路連線請求", "description": "Pretty name for behind-the-scene network requests" }, + "loggerCurrentTab": { + "message": "目前分頁", + "description": "Appears in the logger's tab selector" + }, "logFilterPrompt": { "message": "過濾日誌項目", "description": "English: filter log entries" diff --git a/src/background.html b/src/background.html index d3ccd04992c7f..603027fe1c7a3 100644 --- a/src/background.html +++ b/src/background.html @@ -22,7 +22,10 @@ + + + @@ -31,6 +34,7 @@ + diff --git a/src/css/logger-ui.css b/src/css/logger-ui.css index a7c5252586ed6..5a90028afd8b0 100644 --- a/src/css/logger-ui.css +++ b/src/css/logger-ui.css @@ -128,19 +128,19 @@ textarea { width: 4.6em; } #netInspector table > colgroup > col:nth-of-type(2) { - width: 2.2em; + width: 2.1em; } #netInspector table > colgroup > col:nth-of-type(3) { width: 20%; } #netInspector table > colgroup > col:nth-of-type(4) { - width: 2.2em; + width: 2.1em; } #netInspector table > colgroup > col:nth-of-type(5) { - width: 6em; + width: 5.8em; } #netInspector table > colgroup > col:nth-of-type(6) { - width: calc(100% - 15em - 20%); + width: calc(100% - 14.6em - 20%); } #netInspector.f table tr.f { display: none; @@ -200,13 +200,14 @@ body #netInspector td { #netInspector tr td:last-of-type { border-right: none; } -#netInspector.vCompact td { +#netInspector.vCompact tr:not(.vExpanded) td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } #netInspector tr td:nth-of-type(1) { + cursor: default; text-align: right; white-space: nowrap; } diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js index 7da055c1925bc..baf128e873ea0 100644 --- a/src/js/3p-filters.js +++ b/src/js/3p-filters.js @@ -547,16 +547,9 @@ var fromCloudData = function(data, append) { checked = data.ignoreGenericCosmeticFilters === true || append && elem.checked; elem.checked = listDetails.ignoreGenericCosmeticFilters = checked; - var listKey; - for ( i = 0, n = data.selectedLists.length; i < n; i++ ) { - listKey = data.selectedLists[i]; - if ( listDetails.aliases[listKey] ) { - data.selectedLists[i] = listDetails.aliases[listKey]; - } - } var selectedSet = new Set(data.selectedLists), listEntries = uDom('#lists .listEntry'), - listEntry, input; + listEntry, listKey, input; for ( i = 0, n = listEntries.length; i < n; i++ ) { listEntry = listEntries.at(i); listKey = listEntry.attr('data-listkey'); diff --git a/src/js/assets.js b/src/js/assets.js index da04779dc34e0..d0db484496163 100644 --- a/src/js/assets.js +++ b/src/js/assets.js @@ -53,11 +53,10 @@ api.removeObserver = function(observer) { }; var fireNotification = function(topic, details) { - var result; + var result, r; for ( var i = 0; i < observers.length; i++ ) { - if ( observers[i](topic, details) === false ) { - result = false; - } + r = observers[i](topic, details); + if ( r !== undefined ) { result = r; } } return result; }; @@ -167,155 +166,76 @@ api.fetchText = function(url, onLoad, onError) { } }; -/******************************************************************************* - - TODO(seamless migration): - This block of code will be removed when I am confident all users have - moved to a version of uBO which does not require the old way of caching - assets. - - api.listKeyAliases: a map of old asset keys to new asset keys. +/******************************************************************************/ - migrate(): to seamlessly migrate the old cache manager to the new one: - - attempt to preserve and move content of cached assets to new locations; - - removes all traces of now obsolete cache manager entries in cacheStorage. +// https://github.com/gorhill/uBlock/issues/3331 +// Support the seamless loading of sublists. - This code will typically execute only once, when the newer version of uBO - is first installed and executed. +api.fetchFilterList = function(mainlistURL, onLoad, onError) { + var content = [], + errored = false, + pendingSublistURLs = new Set([ mainlistURL ]), + loadedSublistURLs = new Set(), + toParsedURL = api.fetchFilterList.toParsedURL, + parsedMainURL = toParsedURL(mainlistURL); -**/ + var onLocalLoadSuccess = function(details) { + if ( errored ) { return; } -api.listKeyAliases = { - "assets/thirdparties/publicsuffix.org/list/effective_tld_names.dat": "public_suffix_list.dat", - "assets/user/filters.txt": "user-filters", - "assets/ublock/resources.txt": "ublock-resources", - "assets/ublock/filters.txt": "ublock-filters", - "assets/ublock/privacy.txt": "ublock-privacy", - "assets/ublock/unbreak.txt": "ublock-unbreak", - "assets/ublock/badware.txt": "ublock-badware", - "assets/ublock/experimental.txt": "ublock-experimental", - "https://easylist-downloads.adblockplus.org/easylistchina.txt": "CHN-0", - "https://raw.githubusercontent.com/cjx82630/cjxlist/master/cjxlist.txt": "CHN-1", - "https://raw.githubusercontent.com/cjx82630/cjxlist/master/cjx-annoyance.txt": "CHN-2", - "https://easylist-downloads.adblockplus.org/easylistgermany.txt": "DEU-0", - "https://adblock.dk/block.csv": "DNK-0", - "assets/thirdparties/easylist-downloads.adblockplus.org/easylist.txt": "easylist", - "https://easylist-downloads.adblockplus.org/easylist_noelemhide.txt": "easylist-nocosmetic", - "assets/thirdparties/easylist-downloads.adblockplus.org/easyprivacy.txt": "easyprivacy", - "https://easylist-downloads.adblockplus.org/fanboy-annoyance.txt": "fanboy-annoyance", - "https://easylist-downloads.adblockplus.org/fanboy-social.txt": "fanboy-social", - "https://easylist-downloads.adblockplus.org/liste_fr.txt": "FRA-0", - "http://adblock.gardar.net/is.abp.txt": "ISL-0", - "https://easylist-downloads.adblockplus.org/easylistitaly.txt": "ITA-0", - "https://dl.dropboxusercontent.com/u/1289327/abpxfiles/filtri.txt": "ITA-1", - "https://easylist-downloads.adblockplus.org/advblock.txt": "RUS-0", - "https://easylist-downloads.adblockplus.org/bitblock.txt": "RUS-1", - "https://filters.adtidy.org/extension/chromium/filters/1.txt": "RUS-2", - "https://adguard.com/en/filter-rules.html?id=1": "RUS-2", - "https://easylist-downloads.adblockplus.org/easylistdutch.txt": "NLD-0", - "https://notabug.org/latvian-list/adblock-latvian/raw/master/lists/latvian-list.txt": "LVA-0", - "http://hosts-file.net/.%5Cad_servers.txt": "hphosts", - "http://adblock.ee/list.php": "EST-0", - "https://s3.amazonaws.com/lists.disconnect.me/simple_malvertising.txt": "disconnect-malvertising", - "https://s3.amazonaws.com/lists.disconnect.me/simple_malware.txt": "disconnect-malware", - "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt": "disconnect-tracking", - "https://www.certyficate.it/adblock/adblock.txt": "POL-0", - "https://raw.githubusercontent.com/MajkiIT/polish-ads-filter/master/polish-adblock-filters/adblock.txt": "POL-0", - "https://easylist-downloads.adblockplus.org/antiadblockfilters.txt": "awrl-0", - "http://adb.juvander.net/Finland_adb.txt": "FIN-0", - "https://raw.githubusercontent.com/gfmaster/adblock-korea-contrib/master/filter.txt": "KOR-0", - "https://raw.githubusercontent.com/yous/YousList/master/youslist.txt": "KOR-1", - "https://www.fanboy.co.nz/fanboy-korean.txt": "KOR-2", - "https://raw.githubusercontent.com/heradhis/indonesianadblockrules/master/subscriptions/abpindo.txt": "IDN-0", - "https://raw.githubusercontent.com/ABPindo/indonesianadblockrules/master/subscriptions/abpindo.txt": "IDN-0", - "https://raw.githubusercontent.com/k2jp/abp-japanese-filters/master/abpjf.txt": "JPN-0", - "https://raw.githubusercontent.com/liamja/Prebake/master/obtrusive.txt": "EU-prebake", - "https://easylist-downloads.adblockplus.org/Liste_AR.txt": "ara-0", - "http://margevicius.lt/easylistlithuania.txt": "LTU-0", - "assets/thirdparties/www.malwaredomainlist.com/hostslist/hosts.txt": "malware-0", - "assets/thirdparties/mirror1.malwaredomains.com/files/justdomains": "malware-1", - "http://malwaredomains.lehigh.edu/files/immortal_domains.txt": "malware-2", - "assets/thirdparties/pgl.yoyo.org/as/serverlist": "plowe-0", - "https://raw.githubusercontent.com/easylist/EasyListHebrew/master/EasyListHebrew.txt": "ISR-0", - "https://raw.githubusercontent.com/reek/anti-adblock-killer/master/anti-adblock-killer-filters.txt": "reek-0", - "https://raw.githubusercontent.com/szpeter80/hufilter/master/hufilter.txt": "HUN-0", - "https://raw.githubusercontent.com/tomasko126/easylistczechandslovak/master/filters.txt": "CZE-0", - "http://someonewhocares.org/hosts/hosts": "dpollock-0", - "https://raw.githubusercontent.com/Dawsey21/Lists/master/adblock-list.txt": "spam404-0", - "http://stanev.org/abp/adblock_bg.txt": "BGR-0", - "http://winhelp2002.mvps.org/hosts.txt": "mvps-0", - "https://www.fanboy.co.nz/enhancedstats.txt": "fanboy-enhanced", - "https://www.fanboy.co.nz/fanboy-antifacebook.txt": "fanboy-thirdparty_social", - "https://easylist-downloads.adblockplus.org/easylistspanish.txt": "spa-0", - "https://www.fanboy.co.nz/fanboy-swedish.txt": "SWE-0", - "https://www.fanboy.co.nz/r/fanboy-ultimate.txt": "fanboy-ultimate", - "https://filters.adtidy.org/extension/chromium/filters/13.txt": "TUR-0", - "https://adguard.com/filter-rules.html?id=13": "TUR-0", - "https://www.fanboy.co.nz/fanboy-vietnam.txt": "VIE-0", - "https://www.void.gr/kargig/void-gr-filters.txt": "GRC-0", - "https://raw.githubusercontent.com/betterwebleon/slovenian-list/master/filters.txt": "SVN-0" -}; - -var migrate = function(callback) { - var entries, - moveCount = 0, - toRemove = []; - - var countdown = function(change) { - moveCount -= (change || 0); - if ( moveCount !== 0 ) { return; } - vAPI.cacheStorage.remove(toRemove); - saveAssetCacheRegistry(); - callback(); - }; + var isSublist = details.url !== mainlistURL, + sublistURL; - var onContentRead = function(oldKey, newKey, bin) { - var content = bin && bin['cached_asset_content://' + oldKey] || undefined; - if ( content ) { - assetCacheRegistry[newKey] = { - readTime: Date.now(), - writeTime: entries[oldKey] - }; - if ( reIsExternalPath.test(oldKey) ) { - assetCacheRegistry[newKey].remoteURL = oldKey; + pendingSublistURLs.delete(details.url); + loadedSublistURLs.add(details.url); + if ( isSublist ) { content.push('\n! ' + '>>>>>>>> ' + details.url); } + content.push(details.content.trim()); + if ( isSublist ) { content.push('! <<<<<<<< ' + details.url); } + if ( + parsedMainURL !== undefined && + parsedMainURL.pathname.length > 0 + ) { + var reInclude = /^!#include +(\S+)/gm, + match, subURL; + for (;;) { + match = reInclude.exec(details.content); + if ( match === null ) { break; } + if ( toParsedURL(match[1]) !== undefined ) { continue; } + if ( match[1].indexOf('..') !== -1 ) { continue; } + subURL = + parsedMainURL.origin + + parsedMainURL.pathname.replace(/[^/]+$/, match[1]); + if ( loadedSublistURLs.has(subURL) ) { continue; } + pendingSublistURLs.add(subURL); } - bin = {}; - bin['cache/' + newKey] = content; - vAPI.cacheStorage.set(bin); } - countdown(1); - }; - var onEntries = function(bin) { - entries = bin && bin['cached_asset_entries']; - if ( !entries ) { return callback(); } - if ( bin && bin['assetCacheRegistry'] ) { - assetCacheRegistry = bin['assetCacheRegistry']; - } - var aliases = api.listKeyAliases; - for ( var oldKey in entries ) { - if ( oldKey.endsWith('assets/user/filters.txt') ) { continue; } - var newKey = aliases[oldKey]; - if ( !newKey && /^https?:\/\//.test(oldKey) ) { - newKey = oldKey; + if ( pendingSublistURLs.size !== 0 ) { + for ( sublistURL of pendingSublistURLs ) { + api.fetchText(sublistURL, onLocalLoadSuccess, onLocalLoadError); } - if ( newKey ) { - vAPI.cacheStorage.get( - 'cached_asset_content://' + oldKey, - onContentRead.bind(null, oldKey, newKey) - ); - moveCount += 1; - } - toRemove.push('cached_asset_content://' + oldKey); + return; } - toRemove.push('cached_asset_entries', 'extensionLastVersion'); - countdown(); + + details.url = mainlistURL; + details.content = content.join('\n').trim(); + onLoad(details); }; - vAPI.cacheStorage.get( - [ 'cached_asset_entries', 'assetCacheRegistry' ], - onEntries - ); + var onLocalLoadError = function(details) { + errored = true; + details.url = mainlistURL; + details.content = ''; + onError(details); + }; + + this.fetchText(mainlistURL, onLocalLoadSuccess, onLocalLoadError); +}; + +api.fetchFilterList.toParsedURL = function(url) { + try { + return new URL(url); + } catch (ex) { + } }; /******************************************************************************* @@ -523,16 +443,12 @@ var getAssetCacheRegistry = function(callback) { } }; - var migrationDone = function() { - vAPI.cacheStorage.get('assetCacheRegistry', function(bin) { - if ( bin && bin.assetCacheRegistry ) { - assetCacheRegistry = bin.assetCacheRegistry; - } - registryReady(); - }); - }; - - migrate(migrationDone); + vAPI.cacheStorage.get('assetCacheRegistry', function(bin) { + if ( bin && bin.assetCacheRegistry ) { + assetCacheRegistry = bin.assetCacheRegistry; + } + registryReady(); + }); }; var saveAssetCacheRegistry = (function() { @@ -806,7 +722,11 @@ api.get = function(assetKey, options, callback) { if ( !contentURL ) { return reportBack('', 'E_NOTFOUND'); } - api.fetchText(contentURL, onContentLoaded, onContentNotLoaded); + if ( assetDetails.content === 'filters' ) { + api.fetchFilterList(contentURL, onContentLoaded, onContentNotLoaded); + } else { + api.fetchText(contentURL, onContentLoaded, onContentNotLoaded); + } }; var onContentLoaded = function(details) { @@ -890,7 +810,11 @@ var getRemote = function(assetKey, callback) { if ( !contentURL ) { return reportBack('', 'E_NOTFOUND'); } - api.fetchText(contentURL, onRemoteContentLoaded, onRemoteContentError); + if ( assetDetails.content === 'filters' ) { + api.fetchFilterList(contentURL, onRemoteContentLoaded, onRemoteContentError); + } else { + api.fetchText(contentURL, onRemoteContentLoaded, onRemoteContentError); + } }; getAssetSourceRegistry(function(registry) { @@ -1030,7 +954,7 @@ var updateNext = function() { fireNotification( 'before-asset-updated', { assetKey: assetKey, type: assetEntry.content } - ) !== false + ) === true ) { return assetKey; } @@ -1087,7 +1011,9 @@ var updateDone = function() { api.updateStart = function(details) { var oldUpdateDelay = updaterAssetDelay, - newUpdateDelay = details.delay || updaterAssetDelayDefault; + newUpdateDelay = typeof details.delay === 'number' ? + details.delay : + updaterAssetDelayDefault; updaterAssetDelay = Math.min(oldUpdateDelay, newUpdateDelay); if ( updaterStatus !== undefined ) { if ( newUpdateDelay < oldUpdateDelay ) { diff --git a/src/js/background.js b/src/js/background.js index a60e2ea05b9de..3d9a33018c3d1 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -95,6 +95,7 @@ var µBlock = (function() { // jshint ignore:line // Features detection. privacySettingsSupported: vAPI.browserSettings instanceof Object, cloudStorageSupported: vAPI.cloud instanceof Object, + canFilterResponseBody: vAPI.net.canFilterResponseBody === true, // https://github.com/chrisaljoudi/uBlock/issues/180 // Whitelist directives need to be loaded once the PSL is available @@ -120,8 +121,8 @@ var µBlock = (function() { // jshint ignore:line // read-only systemSettings: { - compiledMagic: 'vrgorlgelgws', - selfieMagic: 'vrgorlgelgws' + compiledMagic: 'puuijtkfpspv', + selfieMagic: 'puuijtkfpspv' }, restoreBackupSettings: { diff --git a/src/js/cloud-ui.js b/src/js/cloud-ui.js index 45a1f73a0b7a2..d0a76bb7a6268 100644 --- a/src/js/cloud-ui.js +++ b/src/js/cloud-ui.js @@ -188,8 +188,6 @@ var onInitialize = function(options) { } self.cloud.options = options; - fetchCloudData(); - var xhr = new XMLHttpRequest(); xhr.open('GET', 'cloud-ui.html', true); xhr.overrideMimeType('text/html;charset=utf-8'); @@ -214,6 +212,10 @@ var onInitialize = function(options) { uDom('#cloudCog').on('click', openOptions); uDom('#cloudOptions').on('click', closeOptions); uDom('#cloudOptionsSubmit').on('click', submitOptions); + + // Patch 2018-01-05: Must not assume this XHR will always be faster + // than messaging + fetchCloudData(); }; xhr.send(); }; diff --git a/src/js/contentscript.js b/src/js/contentscript.js index a231524af8233..eb97b75fabd14 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -374,21 +374,12 @@ vAPI.DOMFilterer = (function() { // 'P' stands for 'Procedural' - var PSelectorHasTask = function(task) { - this.selector = task[1]; - }; - PSelectorHasTask.prototype.exec = function(input) { - var output = []; - for ( var node of input ) { - if ( node.querySelector(this.selector) !== null ) { - output.push(node); - } - } - return output; - }; - var PSelectorHasTextTask = function(task) { - this.needle = new RegExp(task[1]); + var arg0 = task[1], arg1; + if ( Array.isArray(task[1]) ) { + arg1 = arg0[1]; arg0 = arg0[0]; + } + this.needle = new RegExp(arg0, arg1); }; PSelectorHasTextTask.prototype.exec = function(input) { var output = []; @@ -423,7 +414,11 @@ vAPI.DOMFilterer = (function() { var PSelectorMatchesCSSTask = function(task) { this.name = task[1].name; - this.value = new RegExp(task[1].value); + var arg0 = task[1].value, arg1; + if ( Array.isArray(arg0) ) { + arg1 = arg0[1]; arg0 = arg0[0]; + } + this.value = new RegExp(arg0, arg1); }; PSelectorMatchesCSSTask.prototype.pseudo = null; PSelectorMatchesCSSTask.prototype.exec = function(input) { @@ -478,7 +473,7 @@ vAPI.DOMFilterer = (function() { var PSelector = function(o) { if ( PSelector.prototype.operatorToTaskMap === undefined ) { PSelector.prototype.operatorToTaskMap = new Map([ - [ ':has', PSelectorHasTask ], + [ ':has', PSelectorIfTask ], [ ':has-text', PSelectorHasTextTask ], [ ':if', PSelectorIfTask ], [ ':if-not', PSelectorIfNotTask ], @@ -1379,20 +1374,9 @@ vAPI.domSurveyor = (function() { // Library of resources is located at: // https://github.com/gorhill/uBlock/blob/master/assets/ublock/resources.txt - if ( cfeDetails.scripts ) { - // Have the injected script tag remove itself when execution completes: - // to keep DOM as clean as possible. - var text = cfeDetails.scripts + - "\n" + - "(function() {\n" + - " var c = document.currentScript,\n" + - " p = c && c.parentNode;\n" + - " if ( p ) {\n" + - " p.removeChild(c);\n" + - " }\n" + - "})();"; - vAPI.injectScriptlet(document, text); - vAPI.injectedScripts = text; + if ( response.scriptlets ) { + vAPI.injectScriptlet(document, response.scriptlets); + vAPI.injectedScripts = response.scriptlets; } if ( vAPI.domSurveyor instanceof Object ) { @@ -1414,14 +1398,13 @@ vAPI.domSurveyor = (function() { }; // This starts bootstrap process. - var url = window.location.href; vAPI.messaging.send( 'contentscript', { what: 'retrieveContentScriptParameters', - pageURL: url, - locationURL: url, - isRootFrame: window === window.top + url: window.location.href, + isRootFrame: window === window.top, + charset: document.characterSet }, bootstrapPhase1 ); diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index 9baf3a5ee052d..d634b93a105b4 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -19,9 +19,6 @@ Home: https://github.com/gorhill/uBlock */ -/* jshint bitwise: false */ -/* global punycode */ - 'use strict'; /******************************************************************************/ @@ -31,56 +28,6 @@ /******************************************************************************/ var µb = µBlock; - -/******************************************************************************/ - -var isValidCSSSelector = (function() { - var div = document.createElement('div'), - matchesFn; - // Keep in mind: - // https://github.com/gorhill/uBlock/issues/693 - // https://github.com/gorhill/uBlock/issues/1955 - if ( div.matches instanceof Function ) { - matchesFn = div.matches.bind(div); - } else if ( div.mozMatchesSelector instanceof Function ) { - matchesFn = div.mozMatchesSelector.bind(div); - } else if ( div.webkitMatchesSelector instanceof Function ) { - matchesFn = div.webkitMatchesSelector.bind(div); - } else if ( div.msMatchesSelector instanceof Function ) { - matchesFn = div.msMatchesSelector.bind(div); - } else { - matchesFn = div.querySelector.bind(div); - } - // https://github.com/gorhill/uBlock/issues/3111 - // Workaround until https://bugzilla.mozilla.org/show_bug.cgi?id=1406817 - // is fixed. - try { - matchesFn(':scope'); - } catch (ex) { - matchesFn = div.querySelector.bind(div); - } - return function(s) { - try { - matchesFn(s + ', ' + s + ':not(#foo)'); - } catch (ex) { - return false; - } - return true; - }; -})(); - -var reIsRegexLiteral = /^\/.+\/$/; - -var isBadRegex = function(s) { - try { - void new RegExp(s); - } catch (ex) { - isBadRegex.message = ex.toString(); - return true; - } - return false; -}; - var cosmeticSurveyingMissCountMax = parseInt(vAPI.localStorage.getItem('cosmeticSurveyingMissCountMax'), 10) || 15; /******************************************************************************/ @@ -160,7 +107,9 @@ var FilterHostname = function(s, hostname) { FilterHostname.prototype.fid = 8; FilterHostname.prototype.retrieve = function(hostname, out) { - if ( hostname.endsWith(this.hostname) ) { + if ( hostname.endsWith(this.hostname) === false ) { return; } + var i = hostname.length - this.hostname.length; + if ( i === 0 || hostname.charCodeAt(i-1) === 0x2E /* '.' */ ) { out.add(this.s); } }; @@ -222,174 +171,6 @@ registerFilterClass(FilterBucket); /******************************************************************************/ /******************************************************************************/ -var FilterParser = function() { - this.prefix = this.suffix = ''; - this.unhide = 0; - this.hostnames = []; - this.invalid = false; - this.cosmetic = true; - this.reNeedHostname = /^(?:script:contains|script:inject|.+?:-abp-contains|.+?:-abp-has|.+?:contains|.+?:has|.+?:has-text|.+?:if|.+?:if-not|.+?:matches-css(?:-before|-after)?|.*?:xpath)\(.+\)$/; -}; - -/******************************************************************************/ - -FilterParser.prototype.reset = function() { - this.raw = ''; - this.prefix = this.suffix = ''; - this.unhide = 0; - this.hostnames.length = 0; - this.invalid = false; - this.cosmetic = true; - return this; -}; - -/******************************************************************************/ - -FilterParser.prototype.parse = function(raw) { - // important! - this.reset(); - - this.raw = raw; - - // Find the bounds of the anchor. - var lpos = raw.indexOf('#'); - if ( lpos === -1 ) { - this.cosmetic = false; - return this; - } - var rpos = raw.indexOf('#', lpos + 1); - if ( rpos === -1 ) { - this.cosmetic = false; - return this; - } - - // Coarse-check that the anchor is valid. - // `##`: l = 1 - // `#@#`, `#$#`, `#%#`, `#?#`: l = 2 - // `#@$#`, `#@%#`, `#@?#`: l = 3 - if ( (rpos - lpos) > 3 ) { - this.cosmetic = false; - return this; - } - - // Find out type of cosmetic filter. - // Exception filter? - if ( raw.charCodeAt(lpos + 1) === 0x40 /* '@' */ ) { - this.unhide = 1; - } - - // https://github.com/gorhill/uBlock/issues/952 - // Find out whether we are dealing with an Adguard-specific cosmetic - // filter, and if so, translate it if supported, or discard it if not - // supported. - var cCode = raw.charCodeAt(rpos - 1); - if ( cCode !== 0x23 /* '#' */ && cCode !== 0x40 /* '@' */ ) { - // We have an Adguard/ABP cosmetic filter if and only if the character - // is `$`, `%` or `?`, otherwise it's not a cosmetic filter. - if ( - cCode !== 0x24 /* '$' */ && - cCode !== 0x25 /* '%' */ && - cCode !== 0x3F /* '?' */ - ) { - this.cosmetic = false; - return this; - } - // Adguard's scriptlet injection: not supported. - if ( cCode === 0x25 /* '%' */ ) { - this.invalid = true; - return this; - } - // Adguard's style injection: supported, but translate to uBO's format. - if ( cCode === 0x24 /* '$' */ ) { - raw = this.translateAdguardCSSInjectionFilter(raw); - if ( raw === '' ) { - this.invalid = true; - return this; - } - } - rpos = raw.indexOf('#', lpos + 1); - } - - // Extract the hostname(s). - if ( lpos !== 0 ) { - this.prefix = raw.slice(0, lpos); - } - - // Extract the selector. - this.suffix = raw.slice(rpos + 1).trim(); - if ( this.suffix.length === 0 ) { - this.cosmetic = false; - return this; - } - - // 2014-05-23: - // https://github.com/gorhill/httpswitchboard/issues/260 - // Any sequence of `#` longer than one means the line is not a valid - // cosmetic filter. - if ( this.suffix.indexOf('##') !== -1 ) { - this.cosmetic = false; - return this; - } - - // Normalize high-medium selectors: `href` is assumed to imply `a` tag. We - // need to do this here in order to correctly avoid duplicates. The test - // is designed to minimize overhead -- this is a low occurrence filter. - if ( this.suffix.startsWith('[href^="', 1) ) { - this.suffix = this.suffix.slice(1); - } - - if ( this.prefix !== '' ) { - this.hostnames = this.prefix.split(/\s*,\s*/); - } - - // For some selectors, it is mandatory to have a hostname or entity: - // ##script:contains(...) - // ##script:inject(...) - // ##.foo:-abp-contains(...) - // ##.foo:-abp-has(...) - // ##.foo:contains(...) - // ##.foo:has(...) - // ##.foo:has-text(...) - // ##.foo:if(...) - // ##.foo:if-not(...) - // ##.foo:matches-css(...) - // ##.foo:matches-css-after(...) - // ##.foo:matches-css-before(...) - // ##:xpath(...) - if ( - this.hostnames.length === 0 && - this.unhide === 0 && - this.reNeedHostname.test(this.suffix) - ) { - this.invalid = true; - return this; - } - - return this; -}; - -/******************************************************************************/ - -// Reference: https://adguard.com/en/filterrules.html#cssInjection - -FilterParser.prototype.translateAdguardCSSInjectionFilter = function(raw) { - var matches = /^([^#]*)#(@?)\$#([^{]+)\{([^}]+)\}$/.exec(raw); - if ( matches === null ) { - return ''; - } - // For now we do not allow generic CSS injections (prolly never). - if ( matches[1] === '' && matches[2] !== '@' ) { - return ''; - } - return matches[1] + - '#' + matches[2] + '#' + - matches[3].trim() + - ':style(' + matches[4].trim() + ')'; -}; - -/******************************************************************************/ -/******************************************************************************/ - var SelectorCacheEntry = function() { this.reset(); }; @@ -538,17 +319,11 @@ SelectorCacheEntry.prototype.retrieve = function(type, out) { /******************************************************************************/ /******************************************************************************/ -// Two Unicode characters: -// T0HHHHHHH HHHHHHHHH -// | | | -// | | | -// | | | -// | | +-- bit 8-0 of FNV -// | | -// | +-- bit 15-9 of FNV -// | -// +-- filter type (0=hide 1=unhide) -// +// 0000HHHHHHHHHHHH +// | +// | +// | +// +-- bit 11-0 of FNV var makeHash = function(token) { // Ref: Given a URL, returns a unique 4-character long hash string @@ -607,7 +382,6 @@ var makeHash = function(token) { var FilterContainer = function() { this.noDomainHash = '-'; - this.parser = new FilterParser(); this.reHasUnicode = /[^\x00-\x7F]/; this.rePlainSelector = /^[#.][\w\\-]+/; this.rePlainSelectorEscaped = /^[#.](?:\\[0-9A-Fa-f]+ |\\.|\w|-)+/; @@ -615,8 +389,26 @@ var FilterContainer = function() { this.reEscapeSequence = /\\([0-9A-Fa-f]+ |.)/g; this.reSimpleHighGeneric1 = /^[a-z]*\[[^[]+]$/; this.reHighMedium = /^\[href\^="https?:\/\/([^"]{8})[^"]*"\]$/; - this.reScriptSelector = /^script:(contains|inject)\((.+)\)$/; - this.punycode = punycode; + this.reNeedHostname = new RegExp([ + '^', + '(?:', + [ + 'script:contains', + '.+?:has', + '.+?:has-text', + '.+?:if', + '.+?:if-not', + '.+?:matches-css(?:-before|-after)?', + '.*?:xpath', + '.+?:style', + '.+?:-abp-contains', // ABP-specific for `:has-text` + '.+?:-abp-has', // ABP-specific for `:if` + '.+?:contains' // Adguard-specific for `:has-text` + ].join('|'), + ')', + '\\(.+\\)', + '$' + ].join('')); this.selectorCache = new Map(); this.selectorCachePruneDelay = 10 * 60 * 1000; // 10 minutes @@ -630,6 +422,9 @@ var FilterContainer = function() { // generic exception filters this.genericDonthideSet = new Set(); + // TODO: Think about reusing µb.staticExtFilteringEngine.HostnameBasedDB + // for both specific and procedural filters. This would require some + // refactoring. // hostname, entity-based filters this.specificFilters = new Map(); this.proceduralFilters = new Map(); @@ -664,8 +459,6 @@ var FilterContainer = function() { mru: new µb.MRUCache(16) }; - this.userScripts = new Map(); - // Short-lived: content is valid only during one function call. These // is to prevent repeated allocation/deallocation overheads -- the // constructors/destructors of javascript Set/Map is assumed to be costlier @@ -673,6 +466,7 @@ var FilterContainer = function() { this.setRegister0 = new Set(); this.setRegister1 = new Set(); this.setRegister2 = new Set(); + this.mapRegister0 = new Map(); this.reset(); }; @@ -682,7 +476,6 @@ var FilterContainer = function() { // Reset all, thus reducing to a minimum memory footprint of the context. FilterContainer.prototype.reset = function() { - this.parser.reset(); this.µburi = µb.URI; this.frozen = false; this.acceptedCount = 0; @@ -718,11 +511,6 @@ FilterContainer.prototype.reset = function() { this.highlyGeneric.complex.dict.clear(); this.highlyGeneric.complex.str = ''; this.highlyGeneric.complex.mru.reset(); - - this.scriptTagFilters = {}; - this.scriptTagFilterCount = 0; - this.userScripts.clear(); - this.userScriptCount = 0; }; /******************************************************************************/ @@ -756,346 +544,9 @@ FilterContainer.prototype.freeze = function() { this.highlyGeneric.simple.str = µb.arrayFrom(this.highlyGeneric.simple.dict).join(',\n'); this.highlyGeneric.complex.str = µb.arrayFrom(this.highlyGeneric.complex.dict).join(',\n'); - this.parser.reset(); - this.compileSelector.reset(); - this.compileProceduralSelector.reset(); this.frozen = true; }; -/******************************************************************************/ - -// https://github.com/chrisaljoudi/uBlock/issues/1004 -// Detect and report invalid CSS selectors. - -// Discard new ABP's `-abp-properties` directive until it is -// implemented (if ever). Unlikely, see: -// https://github.com/gorhill/uBlock/issues/1752 - -// https://github.com/gorhill/uBlock/issues/2624 -// Convert Adguard's `-ext-has='...'` into uBO's `:has(...)`. - -FilterContainer.prototype.compileSelector = (function() { - var reAfterBeforeSelector = /^(.+?)(::?after|::?before)$/, - reStyleSelector = /^(.+?):style\((.+?)\)$/, - reStyleBad = /url\([^)]+\)/, - reScriptSelector = /^script:(contains|inject)\((.+)\)$/, - reExtendedSyntax = /\[-(?:abp|ext)-[a-z-]+=(['"])(?:.+?)(?:\1)\]/, - reExtendedSyntaxParser = /\[-(?:abp|ext)-([a-z-]+)=(['"])(.+?)\2\]/, - div = document.createElement('div'); - - var normalizedExtendedSyntaxOperators = new Map([ - [ 'contains', ':has-text' ], - [ 'has', ':if' ], - [ 'matches-css', ':matches-css' ], - [ 'matches-css-after', ':matches-css-after' ], - [ 'matches-css-before', ':matches-css-before' ], - ]); - - var isValidStyleProperty = function(cssText) { - if ( reStyleBad.test(cssText) ) { return false; } - div.style.cssText = cssText; - if ( div.style.cssText === '' ) { return false; } - div.style.cssText = ''; - return true; - }; - - var entryPoint = function(raw) { - var extendedSyntax = reExtendedSyntax.test(raw); - if ( isValidCSSSelector(raw) && extendedSyntax === false ) { - return raw; - } - - // We rarely reach this point -- majority of selectors are plain - // CSS selectors. - - var matches, operator; - - // Supported Adguard/ABP advanced selector syntax: will translate into - // uBO's syntax before further processing. - // Mind unsupported advanced selector syntax, such as ABP's - // `-abp-properties`. - // Note: extended selector syntax has been deprecated in ABP, in favor - // of the procedural one (i.e. `:operator(...)`). See - // https://issues.adblockplus.org/ticket/5287 - if ( extendedSyntax ) { - while ( (matches = reExtendedSyntaxParser.exec(raw)) !== null ) { - operator = normalizedExtendedSyntaxOperators.get(matches[1]); - if ( operator === undefined ) { return; } - raw = raw.slice(0, matches.index) + - operator + '(' + matches[3] + ')' + - raw.slice(matches.index + matches[0].length); - } - return this.compileSelector(raw); - } - - var selector = raw, - pseudoclass, style; - - // `:style` selector? - if ( (matches = reStyleSelector.exec(selector)) !== null ) { - selector = matches[1]; - style = matches[2]; - } - - // https://github.com/gorhill/uBlock/issues/2448 - // :after- or :before-based selector? - if ( (matches = reAfterBeforeSelector.exec(selector)) ) { - selector = matches[1]; - pseudoclass = matches[2]; - } - - if ( style !== undefined || pseudoclass !== undefined ) { - if ( isValidCSSSelector(selector) === false ) { - return; - } - if ( pseudoclass !== undefined ) { - selector += pseudoclass; - } - if ( style !== undefined ) { - if ( isValidStyleProperty(style) === false ) { return; } - return JSON.stringify({ - raw: raw, - style: [ selector, style ] - }); - } - return JSON.stringify({ - raw: raw, - pseudoclass: true - }); - } - - // `script:` filter? - if ( (matches = reScriptSelector.exec(raw)) !== null ) { - // :inject - if ( matches[1] === 'inject' ) { - return raw; - } - // :contains - if ( - reIsRegexLiteral.test(matches[2]) === false || - isBadRegex(matches[2].slice(1, -1)) === false - ) { - return raw; - } - } - - // Procedural selector? - var compiled; - if ( (compiled = this.compileProceduralSelector(raw)) ) { - return compiled; - } - - µb.logger.writeOne('', 'error', 'Cosmetic filtering – invalid filter: ' + raw); - }; - - entryPoint.reset = function() { - }; - - return entryPoint; -})(); - -/******************************************************************************/ - -FilterContainer.prototype.compileProceduralSelector = (function() { - var reOperatorParser = /(:(?:-abp-contains|-abp-has|contains|has|has-text|if|if-not|matches-css|matches-css-after|matches-css-before|xpath))\(.+\)$/, - reFirstParentheses = /^\(*/, - reLastParentheses = /\)*$/, - reEscapeRegex = /[.*+?^${}()|[\]\\]/g, - reNeedScope = /^\s*[+>~]/; - - var lastProceduralSelector = '', - lastProceduralSelectorCompiled, - regexToRawValue = new Map(); - - var compileCSSSelector = function(s) { - // https://github.com/AdguardTeam/ExtendedCss/issues/31#issuecomment-302391277 - // Prepend `:scope ` if needed. - if ( reNeedScope.test(s) ) { - s = ':scope ' + s; - } - if ( isValidCSSSelector(s) ) { - return s; - } - }; - - var compileText = function(s) { - var reText; - if ( reIsRegexLiteral.test(s) ) { - reText = s.slice(1, -1); - if ( isBadRegex(reText) ) { return; } - } else { - reText = s.replace(reEscapeRegex, '\\$&'); - regexToRawValue.set(reText, s); - } - return reText; - }; - - var compileCSSDeclaration = function(s) { - var name, value, reText, - pos = s.indexOf(':'); - if ( pos === -1 ) { return; } - name = s.slice(0, pos).trim(); - value = s.slice(pos + 1).trim(); - if ( reIsRegexLiteral.test(value) ) { - reText = value.slice(1, -1); - if ( isBadRegex(reText) ) { return; } - } else { - reText = '^' + value.replace(reEscapeRegex, '\\$&') + '$'; - regexToRawValue.set(reText, value); - } - return { name: name, value: reText }; - }; - - var compileConditionalSelector = function(s) { - // https://github.com/AdguardTeam/ExtendedCss/issues/31#issuecomment-302391277 - // Prepend `:scope ` if needed. - if ( reNeedScope.test(s) ) { - s = ':scope ' + s; - } - return compile(s); - }; - - var compileXpathExpression = function(s) { - var dummy; - try { - dummy = document.createExpression(s, null) instanceof XPathExpression; - } catch (e) { - return; - } - return s; - }; - - // https://github.com/gorhill/uBlock/issues/2793 - var normalizedOperators = new Map([ - [ ':-abp-contains', ':has-text' ], - [ ':-abp-has', ':if' ], - [ ':contains', ':has-text' ] - ]); - - var compileArgument = new Map([ - [ ':has', compileCSSSelector ], - [ ':has-text', compileText ], - [ ':if', compileConditionalSelector ], - [ ':if-not', compileConditionalSelector ], - [ ':matches-css', compileCSSDeclaration ], - [ ':matches-css-after', compileCSSDeclaration ], - [ ':matches-css-before', compileCSSDeclaration ], - [ ':xpath', compileXpathExpression ] - ]); - - // https://github.com/gorhill/uBlock/issues/2793#issuecomment-333269387 - // - Normalize (somewhat) the stringified version of procedural cosmetic - // filters -- this increase the likelihood of detecting duplicates given - // that uBO is able to understand syntax specific to other blockers. - // The normalized string version is what is reported in the logger, by - // design. - var decompile = function(compiled) { - var raw = [ compiled.selector ], - tasks = compiled.tasks, - value; - if ( Array.isArray(tasks) ) { - for ( var i = 0, n = tasks.length, task; i < n; i++ ) { - task = tasks[i]; - switch ( task[0] ) { - case ':has': - case ':xpath': - raw.push(task[0], '(', task[1], ')'); - break; - case ':has-text': - value = regexToRawValue.get(task[1]); - if ( value === undefined ) { - value = '/' + task[1] + '/'; - } - raw.push(task[0], '(', value, ')'); - break; - case ':matches-css': - case ':matches-css-after': - case ':matches-css-before': - value = regexToRawValue.get(task[1].value); - if ( value === undefined ) { - value = '/' + task[1].value + '/'; - } - raw.push(task[0], '(', task[1].name, ': ', value, ')'); - break; - case ':if': - case ':if-not': - raw.push(task[0], '(', decompile(task[1]), ')'); - break; - } - } - } - return raw.join(''); - }; - - var compile = function(raw) { - var matches = reOperatorParser.exec(raw); - if ( matches === null ) { - if ( isValidCSSSelector(raw) ) { return { selector: raw }; } - return; - } - var tasks = [], - firstOperand = raw.slice(0, matches.index), - currentOperator = matches[1], - selector = raw.slice(matches.index + currentOperator.length), - currentArgument = '', nextOperand, nextOperator, - depth = 0, opening, closing; - if ( firstOperand !== '' && isValidCSSSelector(firstOperand) === false ) { return; } - for (;;) { - matches = reOperatorParser.exec(selector); - if ( matches !== null ) { - nextOperand = selector.slice(0, matches.index); - nextOperator = matches[1]; - } else { - nextOperand = selector; - nextOperator = ''; - } - opening = reFirstParentheses.exec(nextOperand)[0].length; - closing = reLastParentheses.exec(nextOperand)[0].length; - if ( opening > closing ) { - if ( depth === 0 ) { currentArgument = ''; } - depth += 1; - } else if ( closing > opening && depth > 0 ) { - depth -= 1; - if ( depth === 0 ) { nextOperand = currentArgument + nextOperand; } - } - if ( depth !== 0 ) { - currentArgument += nextOperand + nextOperator; - } else { - currentOperator = normalizedOperators.get(currentOperator) || currentOperator; - currentArgument = compileArgument.get(currentOperator)(nextOperand.slice(1, -1)); - if ( currentArgument === undefined ) { return; } - tasks.push([ currentOperator, currentArgument ]); - currentOperator = nextOperator; - } - if ( nextOperator === '' ) { break; } - selector = selector.slice(matches.index + nextOperator.length); - } - if ( tasks.length === 0 || depth !== 0 ) { return; } - return { selector: firstOperand, tasks: tasks }; - }; - - var entryPoint = function(raw) { - if ( raw === lastProceduralSelector ) { - return lastProceduralSelectorCompiled; - } - lastProceduralSelector = raw; - var compiled = compile(raw); - if ( compiled !== undefined ) { - compiled.raw = decompile(compiled); - compiled = JSON.stringify(compiled); - } - lastProceduralSelectorCompiled = compiled; - return compiled; - }; - - entryPoint.reset = function() { - regexToRawValue = new Map(); - lastProceduralSelector = ''; - lastProceduralSelectorCompiled = undefined; - }; - - return entryPoint; -})(); /******************************************************************************/ @@ -1133,17 +584,12 @@ FilterContainer.prototype.keyFromSelector = function(selector) { /******************************************************************************/ -FilterContainer.prototype.compile = function(s, writer) { - var parsed = this.parser.parse(s); - if ( parsed.cosmetic === false ) { - return false; - } - if ( parsed.invalid ) { - return true; - } +FilterContainer.prototype.compile = function(parsed, writer) { + // 1000 = cosmetic filtering + writer.select(1000); - var hostnames = parsed.hostnames; - var i = hostnames.length; + var hostnames = parsed.hostnames, + i = hostnames.length; if ( i === 0 ) { this.compileGenericSelector(parsed, writer); return true; @@ -1153,9 +599,8 @@ FilterContainer.prototype.compile = function(s, writer) { // Negated hostname means the filter applies to all non-negated hostnames // of same filter OR globally if there is no non-negated hostnames. var applyGlobally = true; - var hostname; while ( i-- ) { - hostname = hostnames[i]; + var hostname = hostnames[i]; if ( hostname.startsWith('~') === false ) { applyGlobally = false; } @@ -1171,7 +616,7 @@ FilterContainer.prototype.compile = function(s, writer) { /******************************************************************************/ FilterContainer.prototype.compileGenericSelector = function(parsed, writer) { - if ( parsed.unhide === 0 ) { + if ( parsed.exception === false ) { this.compileGenericHideSelector(parsed, writer); } else { this.compileGenericUnhideSelector(parsed, writer); @@ -1181,8 +626,31 @@ FilterContainer.prototype.compileGenericSelector = function(parsed, writer) { /******************************************************************************/ FilterContainer.prototype.compileGenericHideSelector = function(parsed, writer) { - var selector = parsed.suffix, - type = selector.charCodeAt(0), + var selector = parsed.suffix; + + // For some selectors, it is mandatory to have a hostname or entity: + // ##.foo:-abp-contains(...) + // ##.foo:-abp-has(...) + // ##.foo:contains(...) + // ##.foo:has(...) + // ##.foo:has-text(...) + // ##.foo:if(...) + // ##.foo:if-not(...) + // ##.foo:matches-css(...) + // ##.foo:matches-css-after(...) + // ##.foo:matches-css-before(...) + // ##:xpath(...) + // ##.foo:style(...) + if ( this.reNeedHostname.test(selector) ) { + µb.logger.writeOne( + '', + 'error', + 'Cosmetic filtering – invalid generic filter: ##' + selector + ); + return; + } + + var type = selector.charCodeAt(0), key; if ( type === 0x23 /* '#' */ ) { @@ -1196,7 +664,7 @@ FilterContainer.prototype.compileGenericHideSelector = function(parsed, writer) return; } // Complex selector-based CSS rule. - if ( this.compileSelector(selector) !== undefined ) { + if ( µb.staticExtFilteringEngine.compileSelector(selector) !== undefined ) { writer.push([ 1 /* lg+ */, key.slice(1), selector ]); } return; @@ -1213,13 +681,13 @@ FilterContainer.prototype.compileGenericHideSelector = function(parsed, writer) return; } // Complex selector-based CSS rule. - if ( this.compileSelector(selector) !== undefined ) { + if ( µb.staticExtFilteringEngine.compileSelector(selector) !== undefined ) { writer.push([ 3 /* lg+ */, key.slice(1), selector ]); } return; } - var compiled = this.compileSelector(selector); + var compiled = µb.staticExtFilteringEngine.compileSelector(selector); if ( compiled === undefined ) { return; } // TODO: Detect and error on procedural cosmetic filters. @@ -1257,18 +725,12 @@ FilterContainer.prototype.compileGenericHideSelector = function(parsed, writer) /******************************************************************************/ -FilterContainer.prototype.compileGenericUnhideSelector = function(parsed, writer) { - var selector = parsed.suffix; - - // script:contains(...) - // script:inject(...) - if ( this.reScriptSelector.test(selector) ) { - writer.push([ 6 /* js */, '!', '', selector ]); - return; - } - +FilterContainer.prototype.compileGenericUnhideSelector = function( + parsed, + writer +) { // Procedural cosmetic filters are acceptable as generic exception filters. - var compiled = this.compileSelector(selector); + var compiled = µb.staticExtFilteringEngine.compileSelector(parsed.suffix); if ( compiled === undefined ) { return; } // https://github.com/chrisaljoudi/uBlock/issues/497 @@ -1279,37 +741,24 @@ FilterContainer.prototype.compileGenericUnhideSelector = function(parsed, writer /******************************************************************************/ -FilterContainer.prototype.compileHostnameSelector = function(hostname, parsed, writer) { +FilterContainer.prototype.compileHostnameSelector = function( + hostname, + parsed, + writer +) { // https://github.com/chrisaljoudi/uBlock/issues/145 - var unhide = parsed.unhide; + var unhide = parsed.exception ? 1 : 0; if ( hostname.startsWith('~') ) { hostname = hostname.slice(1); unhide ^= 1; } - // punycode if needed - if ( this.reHasUnicode.test(hostname) ) { - hostname = this.punycode.toASCII(hostname); - } + var compiled = µb.staticExtFilteringEngine.compileSelector(parsed.suffix); + if ( compiled === undefined ) { return; } - var selector = parsed.suffix, - domain = this.µburi.domainFromHostname(hostname), + var domain = this.µburi.domainFromHostname(hostname), hash; - // script:contains(...) - // script:inject(...) - if ( this.reScriptSelector.test(selector) ) { - hash = domain !== '' ? domain : this.noDomainHash; - if ( unhide ) { - hash = '!' + hash; - } - writer.push([ 6 /* js */, hash, hostname, selector ]); - return; - } - - var compiled = this.compileSelector(selector); - if ( compiled === undefined ) { return; } - // https://github.com/chrisaljoudi/uBlock/issues/188 // If not a real domain as per PSL, assign a synthetic one if ( hostname.endsWith('.*') === false ) { @@ -1317,7 +766,7 @@ FilterContainer.prototype.compileHostnameSelector = function(hostname, parsed, w } else { hash = makeHash(hostname); } - if ( unhide ) { + if ( unhide === 1 ) { hash = '!' + hash; } @@ -1334,23 +783,22 @@ FilterContainer.prototype.compileHostnameSelector = function(hostname, parsed, w /******************************************************************************/ -FilterContainer.prototype.fromCompiledContent = function( - reader, - skipGenericCosmetic, - skipCosmetic -) { - if ( skipCosmetic ) { +FilterContainer.prototype.fromCompiledContent = function(reader, options) { + if ( options.skipCosmetic ) { this.skipCompiledContent(reader); return; } - if ( skipGenericCosmetic ) { + if ( options.skipGenericCosmetic ) { this.skipGenericCompiledContent(reader); return; } var fingerprint, args, db, filter, bucket; - while ( reader.next() === true ) { + // 1000 = cosmetic filtering + reader.select(1000); + + while ( reader.next() ) { this.acceptedCount += 1; fingerprint = reader.fingerprint(); if ( this.duplicateBuster.has(fingerprint) ) { @@ -1408,12 +856,6 @@ FilterContainer.prototype.fromCompiledContent = function( this.highlyGeneric.complex.dict.add(args[1]); break; - // js, hash, example.com, script:contains(...) - // js, hash, example.com, script:inject(...) - case 6: - this.createScriptFilter(args[1], args[2], args[3]); - break; - // https://github.com/chrisaljoudi/uBlock/issues/497 // Generic exception filters: expected to be a rare occurrence. // #@#.tweet @@ -1449,7 +891,10 @@ FilterContainer.prototype.fromCompiledContent = function( FilterContainer.prototype.skipGenericCompiledContent = function(reader) { var fingerprint, args, db, filter, bucket; - while ( reader.next() === true ) { + // 1000 = cosmetic filtering + reader.select(1000); + + while ( reader.next() ) { this.acceptedCount += 1; fingerprint = reader.fingerprint(); if ( this.duplicateBuster.has(fingerprint) ) { @@ -1461,13 +906,6 @@ FilterContainer.prototype.skipGenericCompiledContent = function(reader) { switch ( args[0] ) { - // js, hash, example.com, script:contains(...) - // js, hash, example.com, script:inject(...) - case 6: - this.duplicateBuster.add(fingerprint); - this.createScriptFilter(args[1], args[2], args[3]); - break; - // https://github.com/chrisaljoudi/uBlock/issues/497 // Generic exception filters: expected to be a rare occurrence. case 7: @@ -1502,241 +940,17 @@ FilterContainer.prototype.skipGenericCompiledContent = function(reader) { /******************************************************************************/ FilterContainer.prototype.skipCompiledContent = function(reader) { - var fingerprint, args; + // 1000 = cosmetic filtering + reader.select(1000); - while ( reader.next() === true ) { + while ( reader.next() ) { this.acceptedCount += 1; - - args = reader.args(); - - // js, hash, example.com, script:contains(...) - // js, hash, example.com, script:inject(...) - if ( args[0] === 6 ) { - fingerprint = reader.fingerprint(); - if ( this.duplicateBuster.has(fingerprint) === false ) { - this.duplicateBuster.add(fingerprint); - this.createScriptFilter(args[1], args[2], args[3]); - } - continue; - } - this.discardedCount += 1; } }; /******************************************************************************/ -FilterContainer.prototype.createScriptFilter = function(hash, hostname, selector) { - if ( selector.startsWith('script:contains') ) { - return this.createScriptTagFilter(hash, hostname, selector); - } - if ( selector.startsWith('script:inject') ) { - return this.createUserScriptRule(hash, hostname, selector); - } -}; - -/******************************************************************************/ - -// 0123456789012345678901 -// script:contains(token) -// ^ ^ -// 16 -1 - -FilterContainer.prototype.createScriptTagFilter = function(hash, hostname, selector) { - var token = selector.slice(16, -1); - token = token.startsWith('/') && token.endsWith('/') - ? token.slice(1, -1) - : token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - - if ( this.scriptTagFilters.hasOwnProperty(hostname) ) { - this.scriptTagFilters[hostname] += '|' + token; - } else { - this.scriptTagFilters[hostname] = token; - } - - this.scriptTagFilterCount += 1; -}; - -/******************************************************************************/ - -FilterContainer.prototype.retrieveScriptTagHostnames = function() { - return Object.keys(this.scriptTagFilters); -}; - -/******************************************************************************/ - -FilterContainer.prototype.retrieveScriptTagRegex = function(domain, hostname) { - if ( this.scriptTagFilterCount === 0 ) { - return; - } - var out = [], hn = hostname, pos; - - // Hostname-based - for (;;) { - if ( this.scriptTagFilters.hasOwnProperty(hn) ) { - out.push(this.scriptTagFilters[hn]); - } - if ( hn === domain ) { - break; - } - pos = hn.indexOf('.'); - if ( pos === -1 ) { - break; - } - hn = hn.slice(pos + 1); - } - - // Entity-based - pos = domain.indexOf('.'); - if ( pos !== -1 ) { - hn = domain.slice(0, pos) + '.*'; - if ( this.scriptTagFilters.hasOwnProperty(hn) ) { - out.push(this.scriptTagFilters[hn]); - } - } - if ( out.length !== 0 ) { - return out.join('|'); - } -}; - -/******************************************************************************/ - -// userScripts{hash} => FilterHostname | FilterBucket - -FilterContainer.prototype.createUserScriptRule = function(hash, hostname, selector) { - var filter = new FilterHostname(selector, hostname); - var bucket = this.userScripts.get(hash); - if ( bucket === undefined ) { - this.userScripts.set(hash, filter); - } else if ( bucket instanceof FilterBucket ) { - bucket.add(filter); - } else { - this.userScripts.set(hash, new FilterBucket(bucket, filter)); - } - this.userScriptCount += 1; -}; - -/******************************************************************************/ - -// https://github.com/gorhill/uBlock/issues/1954 - -// 01234567890123456789 -// script:inject(token[, arg[, ...]]) -// ^ ^ -// 14 -1 - -FilterContainer.prototype.retrieveUserScripts = function(domain, hostname) { - if ( this.userScriptCount === 0 ) { return; } - if ( µb.hiddenSettings.ignoreScriptInjectFilters === true ) { return; } - - var reng = µb.redirectEngine; - if ( !reng ) { return; } - - var out = [], - scripts = new Map(), - pos = domain.indexOf('.'), - entity = pos !== -1 ? domain.slice(0, pos) + '.*' : ''; - - // Implicit - var hn = hostname; - for (;;) { - this._lookupUserScript(scripts, hn + '.js', reng, out); - if ( hn === domain ) { break; } - pos = hn.indexOf('.'); - if ( pos === -1 ) { break; } - hn = hn.slice(pos + 1); - } - if ( entity !== '' ) { - this._lookupUserScript(scripts, entity + '.js', reng, out); - } - - // Explicit (hash is domain). - var selectors = new Set(), - bucket; - if ( (bucket = this.userScripts.get(domain)) ) { - bucket.retrieve(hostname, selectors); - } - if ( entity !== '' && (bucket = this.userScripts.get(entity)) ) { - bucket.retrieve(entity, selectors); - } - for ( var selector of selectors ) { - this._lookupUserScript(scripts, selector.slice(14, -1).trim(), reng, out); - } - - if ( out.length === 0 ) { - return; - } - - // https://github.com/gorhill/uBlock/issues/2835 - // Do not inject scriptlets if the site is under an `allow` rule. - if ( - µb.userSettings.advancedUserEnabled === true && - µb.sessionFirewall.evaluateCellZY(hostname, hostname, '*') === 2 - ) { - return; - } - - // Exceptions should be rare, so we check for exception only if there are - // scriptlets returned. - var exceptions = new Set(), - j, token; - if ( (bucket = this.userScripts.get('!' + domain)) ) { - bucket.retrieve(hostname, exceptions); - } - if ( entity !== '' && (bucket = this.userScripts.get('!' + entity)) ) { - bucket.retrieve(hostname, exceptions); - } - for ( var exception of exceptions ) { - token = exception.slice(14, -1); - if ( (j = scripts.get(token)) !== undefined ) { - out[j] = '// User script "' + token + '" excepted.\n'; - } - } - - return out.join('\n'); -}; - -FilterContainer.prototype._lookupUserScript = function(dict, raw, reng, out) { - if ( dict.has(raw) ) { return; } - var token, args, - pos = raw.indexOf(','); - if ( pos === -1 ) { - token = raw; - } else { - token = raw.slice(0, pos).trim(); - args = raw.slice(pos + 1).trim(); - } - var content = reng.resourceContentFromName(token, 'application/javascript'); - if ( !content ) { return; } - if ( args ) { - content = this._fillupUserScript(content, args); - if ( !content ) { return; } - } - dict.set(raw, out.length); - out.push(content); -}; - -// Fill template placeholders. Return falsy if: -// - At least one argument contains anything else than /\w/ and `.` - -FilterContainer.prototype._fillupUserScript = function(content, args) { - var i = 1, - pos, arg; - while ( args !== '' ) { - pos = args.indexOf(','); - if ( pos === -1 ) { pos = args.length; } - arg = args.slice(0, pos).trim().replace(this._reEscapeScriptArg, '\\$&'); - content = content.replace('{{' + i + '}}', arg); - args = args.slice(pos + 1).trim(); - i++; - } - return content; -}; - -FilterContainer.prototype._reEscapeScriptArg = /[\\'"]/g; - -/******************************************************************************/ - FilterContainer.prototype.toSelfie = function() { var selfieFromMap = function(map) { var selfie = []; @@ -1759,11 +973,7 @@ FilterContainer.prototype.toSelfie = function() { lowlyGenericCCL: µb.arrayFrom(this.lowlyGeneric.cl.complex), highSimpleGenericHideArray: µb.arrayFrom(this.highlyGeneric.simple.dict), highComplexGenericHideArray: µb.arrayFrom(this.highlyGeneric.complex.dict), - genericDonthideArray: µb.arrayFrom(this.genericDonthideSet), - scriptTagFilters: this.scriptTagFilters, - scriptTagFilterCount: this.scriptTagFilterCount, - userScripts: selfieFromMap(this.userScripts), - userScriptCount: this.userScriptCount + genericDonthideArray: µb.arrayFrom(this.genericDonthideSet) }; }; @@ -1795,10 +1005,6 @@ FilterContainer.prototype.fromSelfie = function(selfie) { this.highlyGeneric.complex.dict = new Set(selfie.highComplexGenericHideArray); this.highlyGeneric.complex.str = selfie.highComplexGenericHideArray.join(',\n'); this.genericDonthideSet = new Set(selfie.genericDonthideArray); - this.scriptTagFilters = selfie.scriptTagFilters; - this.scriptTagFilterCount = selfie.scriptTagFilterCount; - this.userScripts = mapFromSelfie(selfie.userScripts); - this.userScriptCount = selfie.userScriptCount; this.frozen = true; }; @@ -1911,10 +1117,7 @@ FilterContainer.prototype.randomAlphaToken = function() { /******************************************************************************/ -FilterContainer.prototype.retrieveGenericSelectors = function( - request, - sender -) { +FilterContainer.prototype.retrieveGenericSelectors = function(request) { if ( this.acceptedCount === 0 ) { return; } if ( !request.ids && !request.classes ) { return; } @@ -1995,9 +1198,8 @@ FilterContainer.prototype.retrieveGenericSelectors = function( // cosmetic filters now. if ( this.supportsUserStylesheets && - sender instanceof Object && - sender.tab instanceof Object && - typeof sender.frameId === 'number' + request.tabId !== undefined && + request.frameId !== undefined ) { var injected = []; if ( out.simple.length !== 0 ) { @@ -2009,10 +1211,10 @@ FilterContainer.prototype.retrieveGenericSelectors = function( out.complex = []; } out.injected = injected.join(',\n'); - vAPI.insertCSS(sender.tab.id, { + vAPI.insertCSS(request.tabId, { code: out.injected + '\n{display:none!important;}', cssOrigin: 'user', - frameId: sender.frameId, + frameId: request.frameId, runAt: 'document_start' }); } @@ -2030,18 +1232,14 @@ FilterContainer.prototype.retrieveGenericSelectors = function( FilterContainer.prototype.retrieveDomainSelectors = function( request, - sender, options ) { - if ( !request.locationURL ) { return; } - //console.time('cosmeticFilteringEngine.retrieveDomainSelectors'); - var hostname = this.µburi.hostnameFromURI(request.locationURL), - domain = this.µburi.domainFromHostname(hostname) || hostname, - pos = domain.indexOf('.'), - entity = pos === -1 ? '' : domain.slice(0, pos - domain.length) + '.*', - cacheEntry = this.selectorCache.get(hostname); + var hostname = request.hostname, + entity = request.entity, + cacheEntry = this.selectorCache.get(hostname), + entry; // https://github.com/chrisaljoudi/uBlock/issues/587 // out.ready will tell the content script the cosmetic filtering engine is @@ -2053,8 +1251,7 @@ FilterContainer.prototype.retrieveDomainSelectors = function( var out = { ready: this.frozen, hostname: hostname, - domain: domain, - entity: entity, + domain: request.domain, declarativeFilters: [], exceptionFilters: [], hideNodeAttr: this.randomAlphaToken(), @@ -2064,12 +1261,11 @@ FilterContainer.prototype.retrieveDomainSelectors = function( injectedHideFilters: '', networkFilters: '', noDOMSurveying: this.hasGenericHide === false, - proceduralFilters: [], - scripts: undefined + proceduralFilters: [] }; if ( options.noCosmeticFiltering !== true ) { - var domainHash = makeHash(domain), + var domainHash = makeHash(request.domain), entityHash = entity !== '' ? makeHash(entity) : undefined, exception, bucket; @@ -2177,7 +1373,7 @@ FilterContainer.prototype.retrieveDomainSelectors = function( if ( options.noGenericCosmeticFiltering !== true ) { var exceptionHash = out.exceptionFilters.join(); for ( var type in this.highlyGeneric ) { - var entry = this.highlyGeneric[type]; + entry = this.highlyGeneric[type]; var str = entry.mru.lookup(exceptionHash); if ( str === undefined ) { str = { s: entry.str }; @@ -2205,9 +1401,7 @@ FilterContainer.prototype.retrieveDomainSelectors = function( this.setRegister2.clear(); } - // Scriptlet injection. - out.scripts = this.retrieveUserScripts(domain, hostname); - + // CSS selectors for collapsible blocked elements if ( cacheEntry ) { var networkFilters = []; cacheEntry.retrieve('net', networkFilters); @@ -2219,9 +1413,8 @@ FilterContainer.prototype.retrieveDomainSelectors = function( // cosmetic filters now. if ( this.supportsUserStylesheets && - sender instanceof Object && - sender.tab instanceof Object && - typeof sender.frameId === 'number' + request.tabId !== undefined && + request.frameId !== undefined ) { var injectedHideFilters = []; if ( out.declarativeFilters.length !== 0 ) { @@ -2244,16 +1437,16 @@ FilterContainer.prototype.retrieveDomainSelectors = function( var details = { code: '', cssOrigin: 'user', - frameId: sender.frameId, + frameId: request.frameId, runAt: 'document_start' }; if ( out.injectedHideFilters.length !== 0 ) { details.code = out.injectedHideFilters + '\n{display:none!important;}'; - vAPI.insertCSS(sender.tab.id, details); + vAPI.insertCSS(request.tabId, details); } if ( out.networkFilters.length !== 0 ) { details.code = out.networkFilters + '\n{display:none!important;}'; - vAPI.insertCSS(sender.tab.id, details); + vAPI.insertCSS(request.tabId, details); out.networkFilters = ''; } } diff --git a/src/js/html-filtering.js b/src/js/html-filtering.js new file mode 100644 index 0000000000000..f4d2c39126809 --- /dev/null +++ b/src/js/html-filtering.js @@ -0,0 +1,392 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2017 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'; + +/******************************************************************************/ + +µBlock.htmlFilteringEngine = (function() { + var api = {}; + + var µb = µBlock, + filterDB = new µb.staticExtFilteringEngine.HostnameBasedDB(), + pselectors = new Map(), + duplicates = new Set(), + acceptedCount = 0, + discardedCount = 0, + docRegister, loggerRegister; + + var PSelectorHasTextTask = function(task) { + var arg0 = task[1], arg1; + if ( Array.isArray(task[1]) ) { + arg1 = arg0[1]; arg0 = arg0[0]; + } + this.needle = new RegExp(arg0, arg1); + }; + PSelectorHasTextTask.prototype.exec = function(input) { + var output = []; + for ( var node of input ) { + if ( this.needle.test(node.textContent) ) { + output.push(node); + } + } + return output; + }; + + var PSelectorIfTask = function(task) { + this.pselector = new PSelector(task[1]); + }; + PSelectorIfTask.prototype.target = true; + Object.defineProperty(PSelectorIfTask.prototype, 'invalid', { + get: function() { + return this.pselector.invalid; + } + }); + PSelectorIfTask.prototype.exec = function(input) { + var output = []; + for ( var node of input ) { + if ( this.pselector.test(node) === this.target ) { + output.push(node); + } + } + return output; + }; + + var PSelectorIfNotTask = function(task) { + PSelectorIfTask.call(this, task); + this.target = false; + }; + PSelectorIfNotTask.prototype = Object.create(PSelectorIfTask.prototype); + PSelectorIfNotTask.prototype.constructor = PSelectorIfNotTask; + + var PSelectorXpathTask = function(task) { + this.xpe = task[1]; + }; + PSelectorXpathTask.prototype.exec = function(input) { + var output = [], + xpe = docRegister.createExpression(this.xpe, null), + xpr = null; + for ( var node of input ) { + xpr = xpe.evaluate( + node, + XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, + xpr + ); + var j = xpr.snapshotLength; + while ( j-- ) { + node = xpr.snapshotItem(j); + if ( node.nodeType === 1 ) { + output.push(node); + } + } + } + return output; + }; + + var PSelector = function(o) { + if ( PSelector.prototype.operatorToTaskMap === undefined ) { + PSelector.prototype.operatorToTaskMap = new Map([ + [ ':has', PSelectorIfTask ], + [ ':has-text', PSelectorHasTextTask ], + [ ':if', PSelectorIfTask ], + [ ':if-not', PSelectorIfNotTask ], + [ ':xpath', PSelectorXpathTask ] + ]); + } + this.raw = o.raw; + this.selector = o.selector; + this.tasks = []; + var tasks = o.tasks; + if ( !tasks ) { return; } + for ( var task of tasks ) { + var ctor = this.operatorToTaskMap.get(task[0]); + if ( ctor === undefined ) { + this.invalid = true; + break; + } + var pselector = new ctor(task); + if ( pselector instanceof PSelectorIfTask && pselector.invalid ) { + this.invalid = true; + break; + } + this.tasks.push(pselector); + } + }; + PSelector.prototype.operatorToTaskMap = undefined; + PSelector.prototype.invalid = false; + PSelector.prototype.prime = function(input) { + var root = input || docRegister; + if ( this.selector !== '' ) { + return root.querySelectorAll(this.selector); + } + return [ root ]; + }; + PSelector.prototype.exec = function(input) { + if ( this.invalid ) { return []; } + var nodes = this.prime(input); + for ( var task of this.tasks ) { + if ( nodes.length === 0 ) { break; } + nodes = task.exec(nodes); + } + return nodes; + }; + PSelector.prototype.test = function(input) { + if ( this.invalid ) { return false; } + var nodes = this.prime(input), AA = [ null ], aa; + for ( var node of nodes ) { + AA[0] = node; aa = AA; + for ( var task of this.tasks ) { + aa = task.exec(aa); + if ( aa.length === 0 ) { break; } + } + if ( aa.length !== 0 ) { return true; } + } + return false; + }; + + var logOne = function(details, selector) { + loggerRegister.writeOne( + details.tabId, + 'cosmetic', + { source: 'cosmetic', raw: '##^' + selector }, + 'dom', + details.url, + null, + details.hostname + ); + }; + + var applyProceduralSelector = function(details, selector) { + var pselector = pselectors.get(selector); + if ( pselector === undefined ) { + pselector = new PSelector(JSON.parse(selector)); + pselectors.set(selector, pselector); + } + var nodes = pselector.exec(), + i = nodes.length, + modified = false; + while ( i-- ) { + var node = nodes[i]; + if ( node.parentNode !== null ) { + node.parentNode.removeChild(node); + modified = true; + } + } + if ( modified && loggerRegister.isEnabled() ) { + logOne(details, pselector.raw); + } + return modified; + }; + + var applyCSSSelector = function(details, selector) { + var nodes = docRegister.querySelectorAll(selector), + i = nodes.length, + modified = false; + while ( i-- ) { + var node = nodes[i]; + if ( node.parentNode !== null ) { + node.parentNode.removeChild(node); + modified = true; + } + } + if ( modified && loggerRegister.isEnabled() ) { + logOne(details, selector); + } + return modified; + }; + + api.reset = function() { + filterDB.clear(); + pselectors.clear(); + duplicates.clear(); + acceptedCount = 0; + discardedCount = 0; + }; + + api.freeze = function() { + duplicates.clear(); + }; + + api.compile = function(parsed, writer) { + var selector = parsed.suffix.slice(1).trim(), + compiled = µb.staticExtFilteringEngine.compileSelector(selector); + if ( compiled === undefined ) { return; } + + // 1002 = html filtering + writer.select(1002); + + // TODO: Mind negated hostnames, they are currently discarded. + + for ( var hostname of parsed.hostnames ) { + if ( hostname.charCodeAt(0) === 0x7E /* '~' */ ) { continue; } + var domain = µb.URI.domainFromHostname(hostname); + writer.push([ + compiled.charCodeAt(0) !== 0x7B /* '{' */ ? 64 : 65, + parsed.exception ? '!' + domain : domain, + hostname, + compiled + ]); + } + }; + + api.fromCompiledContent = function(reader) { + // Don't bother loading filters if stream filtering is not supported. + //if ( µb.canFilterResponseBody === false ) { return; } + + // 1002 = html filtering + reader.select(1002); + + while ( reader.next() ) { + acceptedCount += 1; + var fingerprint = reader.fingerprint(); + if ( duplicates.has(fingerprint) ) { + discardedCount += 1; + continue; + } + duplicates.add(fingerprint); + var args = reader.args(); + filterDB.add(args[1], { + type: args[0], + hostname: args[2], + selector: args[3] + }); + } + }; + + api.retrieve = function(request) { + var hostname = request.hostname; + + // https://github.com/gorhill/uBlock/issues/2835 + // Do not filter if the site is under an `allow` rule. + if ( + µb.userSettings.advancedUserEnabled && + µb.sessionFirewall.evaluateCellZY(hostname, hostname, '*') === 2 + ) { + return; + } + + var out = []; + if ( request.domain !== '' ) { + filterDB.retrieve(request.domain, hostname, out); + filterDB.retrieve(request.entity, request.entity, out); + } + filterDB.retrieve('', hostname, out); + + // TODO: handle exceptions. + + if ( out.length !== 0 ) { + return out; + } + }; + + api.apply = function(doc, details) { + docRegister = doc; + loggerRegister = µb.logger; + var modified = false; + for ( var entry of details.selectors ) { + if ( entry.type === 64 ) { + if ( applyCSSSelector(details, entry.selector) ) { + modified = true; + } + } else { + if ( applyProceduralSelector(details, entry.selector) ) { + modified = true; + } + } + } + + docRegister = loggerRegister = undefined; + return modified; + }; + + api.toSelfie = function() { + return filterDB.toSelfie(); + }; + + api.fromSelfie = function(selfie) { + filterDB = new µb.staticExtFilteringEngine.HostnameBasedDB(selfie); + pselectors.clear(); + }; + + // TODO: Following methods is useful only to legacy Firefox. This can be + // removed once support for legacy Firefox is dropped. The only care + // at this point is for the code to work, not to be efficient. + // Only `script:has-text` selectors are considered. + + api.retrieveScriptTagHostnames = function() { + var out = new Set(); + for ( var entry of filterDB ) { + if ( entry.type !== 65 ) { continue; } + var o = JSON.parse(entry.selector); + if ( + o.tasks.length === 1 && + o.tasks[0].length === 2 && + o.tasks[0][0] === ':has-text' + ) { + out.add(entry.hostname); + } + } + if ( out.size !== 0 ) { + return Array.from(out); + } + }; + + api.retrieveScriptTagRegex = function(domain, hostname) { + var entries = api.retrieve({ + hostname: hostname, + domain: domain, + entity: µb.URI.entityFromDomain(domain) + }); + if ( entries === undefined ) { return; } + var out = new Set(); + for ( var entry of entries ) { + if ( entry.type !== 65 ) { continue; } + var o = JSON.parse(entry.selector); + if ( + o.tasks.length === 1 && + o.tasks[0].length === 2 && + o.tasks[0][0] === ':has-text' + ) { + out.add(o.tasks[0][1]); + } + } + if ( out.size !== 0 ) { + return Array.from(out).join('|'); + } + }; + + Object.defineProperties(api, { + acceptedCount: { + get: function() { + return acceptedCount; + } + }, + discardedCount: { + get: function() { + return discardedCount; + } + } + }); + + return api; +})(); + +/******************************************************************************/ diff --git a/src/js/logger-ui-inspector.js b/src/js/logger-ui-inspector.js index f598ec2db293b..8b5e3f3816c24 100644 --- a/src/js/logger-ui-inspector.js +++ b/src/js/logger-ui-inspector.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2015-2016 Raymond Hill + Copyright (C) 2015-2018 Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -50,7 +50,6 @@ var inspectedURL = ''; var inspectedHostname = ''; var inspector = uDom.nodeFromId('domInspector'); var domTree = uDom.nodeFromId('domTree'); -var tabSelector = uDom.nodeFromId('pageSelector'); var uidGenerator = 1; var filterToIdMap = new Map(); @@ -79,7 +78,12 @@ messaging.addChannelListener('loggerUI', function(msg) { break; case 'connectionRequested': if ( msg.from !== 'domInspector' ) { return false; } - if ( msg.tabId !== inspectedTabId ) { return false; } + if ( + msg.tabId === undefined || + msg.tabId !== inspectedTabId + ) { + return false; + } filterToIdMap.clear(); logger.removeAllChildren(domTree); inspectorConnectionId = msg.id; @@ -557,8 +561,7 @@ var onMouseOver = (function() { var currentTabId = function() { if ( showdomButton.classList.contains('active') === false ) { return ''; } - var tabId = logger.tabIdFromClassName(tabSelector.value) || ''; - return tabId !== 'bts' ? tabId : ''; + return logger.tabIdFromPageSelector(); }; /******************************************************************************/ @@ -631,7 +634,7 @@ var revert = function() { var toggleOn = function() { window.addEventListener('beforeunload', toggleOff); - tabSelector.addEventListener('change', onTabIdChanged); + document.addEventListener('tabIdChanged', onTabIdChanged); domTree.addEventListener('click', onClicked, true); domTree.addEventListener('mouseover', onMouseOver, true); uDom.nodeFromSelector('#domInspector .vCompactToggler').addEventListener('click', toggleVCompactView); @@ -647,7 +650,7 @@ var toggleOn = function() { var toggleOff = function() { shutdownInspector(); window.removeEventListener('beforeunload', toggleOff); - tabSelector.removeEventListener('change', onTabIdChanged); + document.removeEventListener('tabIdChanged', onTabIdChanged); domTree.removeEventListener('click', onClicked, true); domTree.removeEventListener('mouseover', onMouseOver, true); uDom.nodeFromSelector('#domInspector .vCompactToggler').removeEventListener('click', toggleVCompactView); @@ -675,5 +678,3 @@ showdomButton.addEventListener('click', toggle); /******************************************************************************/ })(); - - diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js index 218f44fb31bb3..9690cafec27bd 100644 --- a/src/js/logger-ui.js +++ b/src/js/logger-ui.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2015-2017 Raymond Hill + Copyright (C) 2015-2018 Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,8 +29,12 @@ /******************************************************************************/ -var logger = self.logger = {}; +var logger = self.logger = { + ownerId: Date.now() +}; + var messaging = vAPI.messaging; +var activeTabId; /******************************************************************************/ @@ -42,12 +46,17 @@ var removeAllChildren = logger.removeAllChildren = function(node) { /******************************************************************************/ -var tabIdFromClassName = logger.tabIdFromClassName = function(className) { - var matches = className.match(/(?:^| )tab_([^ ]+)(?: |$)/); - if ( matches === null ) { - return ''; +var tabIdFromClassName = function(className) { + var matches = className.match(/\btab_([^ ]+)\b/); + return matches !== null ? matches[1] : ''; +}; + +var tabIdFromPageSelector = logger.tabIdFromPageSelector = function() { + var tabClass = uDom.nodeFromId('pageSelector').value; + if ( tabClass === 'tab_active' && activeTabId !== undefined ) { + return activeTabId; } - return matches[1]; + return /^tab_\d+$/.test(tabClass) ? tabClass.slice(4) : ''; }; /******************************************************************************/ @@ -407,15 +416,17 @@ var renderLogEntries = function(response) { /******************************************************************************/ var synchronizeTabIds = function(newTabIds) { + var select = uDom.nodeFromId('pageSelector'); + var selectValue = select.value; + var oldTabIds = allTabIds; - var autoDeleteVoidRows = !!vAPI.localStorage.getItem('loggerAutoDeleteVoidRows'); + var autoDeleteVoidRows = selectValue === 'tab_active'; var rowVoided = false; - var trs; for ( var tabId in oldTabIds ) { if ( oldTabIds.hasOwnProperty(tabId) === false ) { continue; } if ( newTabIds.hasOwnProperty(tabId) ) { continue; } // Mark or remove voided rows - trs = uDom('.tab_' + tabId); + var trs = uDom('.tab_' + tabId); if ( autoDeleteVoidRows ) { toJunkyard(trs); } else { @@ -428,13 +439,11 @@ var synchronizeTabIds = function(newTabIds) { } } - var select = uDom.nodeFromId('pageSelector'); - var selectValue = select.value; var tabIds = Object.keys(newTabIds).sort(function(a, b) { return newTabIds[a].localeCompare(newTabIds[b]); }); var option; - for ( var i = 0, j = 2; i < tabIds.length; i++ ) { + for ( var i = 0, j = 3; i < tabIds.length; i++ ) { tabId = tabIds[i]; if ( tabId === noTabId ) { continue; } option = select.options[j]; @@ -486,9 +495,19 @@ var truncateLog = function(size) { /******************************************************************************/ var onLogBufferRead = function(response) { + if ( !response || response.unavailable ) { + readLogBufferAsync(); + return; + } + // This tells us the behind-the-scene tab id noTabId = response.noTabId; + // Tab id of currently active tab + if ( response.activeTabId ) { + activeTabId = response.activeTabId; + } + // This may have changed meanwhile if ( response.maxEntries !== maxEntries ) { maxEntries = response.maxEntries; @@ -521,7 +540,7 @@ var onLogBufferRead = function(response) { tbody.querySelector('tr') === null ); - vAPI.setTimeout(readLogBuffer, 1200); + readLogBufferAsync(); }; /******************************************************************************/ @@ -531,52 +550,70 @@ var onLogBufferRead = function(response) { // require a bit more code to ensure no multi time out events. var readLogBuffer = function() { - messaging.send('loggerUI', { what: 'readAll' }, onLogBufferRead); + if ( logger.ownerId === undefined ) { return; } + vAPI.messaging.send( + 'loggerUI', + { what: 'readAll', ownerId: logger.ownerId }, + onLogBufferRead + ); }; +var readLogBufferAsync = function() { + if ( logger.ownerId === undefined ) { return; } + vAPI.setTimeout(readLogBuffer, 1200); +}; + /******************************************************************************/ var pageSelectorChanged = function() { - window.location.replace('#' + uDom.nodeFromId('pageSelector').value); + var select = uDom.nodeFromId('pageSelector'); + window.location.replace('#' + select.value); pageSelectorFromURLHash(); }; -/******************************************************************************/ - var pageSelectorFromURLHash = (function() { - var lastHash = ''; + var lastTabClass = ''; + var lastEffectiveTabClass = ''; - return function() { - var hash = window.location.hash; - if ( hash === lastHash ) { - return; + var selectRows = function(tabClass) { + if ( tabClass === 'tab_active' ) { + if ( activeTabId === undefined ) { return; } + tabClass = 'tab_' + activeTabId; } + if ( tabClass === lastEffectiveTabClass ) { return; } + lastEffectiveTabClass = tabClass; + + document.dispatchEvent(new Event('tabIdChanged')); + + var style = uDom.nodeFromId('tabFilterer'); + var sheet = style.sheet; + while ( sheet.cssRules.length !== 0 ) { + sheet.deleteRule(0); + } + if ( tabClass === '' ) { return; } + sheet.insertRule( + '#netInspector tr:not(.' + tabClass + '):not(.tab_bts) ' + + '{display:none;}' + ); + }; + + return function() { + var tabClass = window.location.hash.slice(1); + selectRows(tabClass); + if ( tabClass === lastTabClass ) { return; } + lastTabClass = tabClass; - var tabClass = hash.slice(1); var select = uDom.nodeFromId('pageSelector'); var option = select.querySelector('option[value="' + tabClass + '"]'); if ( option === null ) { - hash = window.location.hash = ''; + window.location.hash = ''; tabClass = ''; option = select.options[0]; } - lastHash = hash; - select.selectedIndex = option.index; select.value = option.value; - var style = uDom.nodeFromId('tabFilterer'); - var sheet = style.sheet; - while ( sheet.cssRules.length !== 0 ) { - sheet.deleteRule(0); - } - if ( tabClass !== '' ) { - sheet.insertRule( - '#netInspector tr:not(.' + tabClass + ') { display: none; }', - 0 - ); - } uDom('.needtab').toggleClass( 'disabled', tabClass === '' || tabClass === 'tab_bts' @@ -586,13 +623,14 @@ var pageSelectorFromURLHash = (function() { /******************************************************************************/ -var reloadTab = function() { - var tabClass = uDom.nodeFromId('pageSelector').value; - var tabId = tabIdFromClassName(tabClass); - if ( tabId === 'bts' || tabId === '' ) { - return; - } - messaging.send('loggerUI', { what: 'reloadTab', tabId: tabId }); +var reloadTab = function(ev) { + var tabId = tabIdFromPageSelector(); + if ( tabId === '' ) { return; } + messaging.send('loggerUI', { + what: 'reloadTab', + tabId: tabId, + bypassCache: ev && (ev.ctrlKey || ev.metaKey || ev.shiftKey) + }); }; /******************************************************************************/ @@ -1475,13 +1513,17 @@ var toJunkyard = function(trs) { /******************************************************************************/ var clearBuffer = function() { - var tabId = uDom.nodeFromId('pageSelector').value || null; + var tabClass = uDom.nodeFromId('pageSelector').value; + var btsAlso = tabClass === '' || tabClass === 'tab_bts'; var tbody = document.querySelector('#netInspector tbody'); var tr = tbody.lastElementChild; var trPrevious; while ( tr !== null ) { trPrevious = tr.previousElementSibling; - if ( tabId === null || tr.classList.contains(tabId) ) { + if ( + (tr.clientHeight > 0) && + (tr.classList.contains('tab_bts') === false || btsAlso) + ) { trJunkyard.push(tbody.removeChild(tr)); } tr = trPrevious; @@ -1511,6 +1553,11 @@ var cleanBuffer = function() { var toggleVCompactView = function() { uDom.nodeFromId('netInspector').classList.toggle('vCompact'); + uDom('#netInspector .vExpanded').toggleClass('vExpanded'); +}; + +var toggleVCompactRow = function(ev) { + ev.target.parentElement.classList.toggle('vExpanded'); }; /******************************************************************************/ @@ -1634,6 +1681,29 @@ var popupManager = (function() { /******************************************************************************/ +var grabView = function() { + if ( logger.ownerId === undefined ) { + logger.ownerId = Date.now(); + } + readLogBufferAsync(); +}; + +var releaseView = function() { + if ( logger.ownerId === undefined ) { return; } + vAPI.messaging.send( + 'loggerUI', + { what: 'releaseView', ownerId: logger.ownerId } + ); + logger.ownerId = undefined; +}; + +window.addEventListener('pagehide', releaseView); +window.addEventListener('pageshow', grabView); +// https://bugzilla.mozilla.org/show_bug.cgi?id=1398625 +window.addEventListener('beforeunload', releaseView); + +/******************************************************************************/ + readLogBuffer(); uDom('#pageSelector').on('change', pageSelectorChanged); @@ -1644,6 +1714,7 @@ uDom('#netInspector .vCompactToggler').on('click', toggleVCompactView); uDom('#clean').on('click', cleanBuffer); uDom('#clear').on('click', clearBuffer); uDom('#maxEntries').on('change', onMaxEntriesChanged); +uDom('#netInspector table').on('click', 'tr > td:nth-of-type(1)', toggleVCompactRow); uDom('#netInspector table').on('click', 'tr.canMtx > td:nth-of-type(2)', popupManager.toggleOn); uDom('#netInspector').on('click', 'tr.canLookup > td:nth-of-type(3)', reverseLookupManager.toggleOn); uDom('#netInspector').on('click', 'tr.cat_net > td:nth-of-type(4)', netFilteringManager.toggleOn); diff --git a/src/js/logger.js b/src/js/logger.js index 37da162a75d33..f4e33f5c84c2c 100644 --- a/src/js/logger.js +++ b/src/js/logger.js @@ -47,7 +47,7 @@ // After 60 seconds without being read, a buffer will be considered // unused, and thus removed from memory. - var logBufferObsoleteAfter = 60 * 1000; + var logBufferObsoleteAfter = 30 * 1000; var janitor = function() { if ( @@ -56,6 +56,7 @@ ) { buffer = null; writePtr = 0; + api.ownerId = undefined; vAPI.messaging.broadcast({ what: 'loggerDisabled' }); } if ( buffer !== null ) { @@ -63,7 +64,8 @@ } }; - return { + var api = { + ownerId: undefined, writeOne: function() { if ( buffer === null ) { return; } if ( writePtr === buffer.length ) { @@ -73,7 +75,8 @@ } writePtr += 1; }, - readAll: function() { + readAll: function(ownerId) { + this.ownerId = ownerId; if ( buffer === null ) { buffer = []; vAPI.setTimeout(janitor, logBufferObsoleteAfter); @@ -88,6 +91,8 @@ } }; + return api; + })(); /******************************************************************************/ diff --git a/src/js/messaging.js b/src/js/messaging.js index 29c783bd46275..7ce4476c65a7d 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2017 Raymond Hill + Copyright (C) 2014-2018 Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -102,7 +102,7 @@ var onMessage = function(request, sender, callback) { break; case 'compileCosmeticFilterSelector': - response = µb.cosmeticFilteringEngine.compileSelector(request.selector); + response = µb.staticExtFilteringEngine.compileSelector(request.selector); break; case 'cosmeticFiltersInjected': @@ -121,7 +121,9 @@ var onMessage = function(request, sender, callback) { case 'forceUpdateAssets': µb.scheduleAssetUpdater(0); - µb.assets.updateStart({ delay: µb.hiddenSettings.manualUpdateAssetFetchPeriod || 2000 }); + µb.assets.updateStart({ + delay: µb.hiddenSettings.manualUpdateAssetFetchPeriod + }); break; case 'getAppData': @@ -155,7 +157,7 @@ var onMessage = function(request, sender, callback) { case 'reloadTab': if ( vAPI.isBehindTheSceneTabId(request.tabId) === false ) { - vAPI.tabs.reload(request.tabId); + vAPI.tabs.reload(request.tabId, request.bypassCache === true); if ( request.select && vAPI.tabs.select ) { vAPI.tabs.select(request.tabId); } @@ -464,11 +466,12 @@ var onMessage = function(request, sender, callback) { // Sync var µb = µBlock, response, - tabId, - pageStore; + tabId, frameId, + pageStore = null; if ( sender && sender.tab ) { tabId = sender.tab.id; + frameId = sender.frameId; pageStore = µb.pageStoreFromTabId(tabId); } @@ -490,27 +493,47 @@ var onMessage = function(request, sender, callback) { break; case 'retrieveContentScriptParameters': - if ( pageStore && pageStore.getNetFilteringSwitch() ) { - response = { - collapseBlocked: µb.userSettings.collapseBlocked, - noCosmeticFiltering: pageStore.noCosmeticFiltering === true, - noGenericCosmeticFiltering: - pageStore.noGenericCosmeticFiltering === true - }; - response.specificCosmeticFilters = - µb.cosmeticFilteringEngine - .retrieveDomainSelectors(request, sender, response); - if ( request.isRootFrame && µb.logger.isEnabled() ) { - µb.logCosmeticFilters(tabId); - } + if ( + pageStore === null || + pageStore.getNetFilteringSwitch() === false || + !request.url + ) { + break; + } + response = { + collapseBlocked: µb.userSettings.collapseBlocked, + noCosmeticFiltering: pageStore.noCosmeticFiltering === true, + noGenericCosmeticFiltering: + pageStore.noGenericCosmeticFiltering === true + }; + request.tabId = tabId; + request.frameId = frameId; + request.hostname = µb.URI.hostnameFromURI(request.url); + request.domain = µb.URI.domainFromHostname(request.hostname); + request.entity = µb.URI.entityFromDomain(request.domain); + response.specificCosmeticFilters = + µb.cosmeticFilteringEngine.retrieveDomainSelectors(request, response); + // If response body filtering is supported, than the scriptlets have + // already been injected. + if ( + µb.canFilterResponseBody === false || + µb.textEncode === undefined || + µb.textEncode.normalizeCharset(request.charset) === undefined + ) { + response.scriptlets = µb.scriptletFilteringEngine.retrieve(request); + } + if ( request.isRootFrame && µb.logger.isEnabled() ) { + µb.logCosmeticFilters(tabId); } break; case 'retrieveGenericCosmeticSelectors': if ( pageStore && pageStore.getGenericCosmeticFilteringSwitch() ) { + request.tabId = tabId; + request.frameId = frameId; response = { result: µb.cosmeticFilteringEngine - .retrieveGenericSelectors(request, sender) + .retrieveGenericSelectors(request) }; } break; @@ -711,15 +734,7 @@ var backupUserData = function(callback) { dynamicFilteringString: µb.permanentFirewall.toString(), urlFilteringString: µb.permanentURLFiltering.toString(), hostnameSwitchesString: µb.hnSwitches.toString(), - userFilters: '', - // TODO(seamless migration): - // The following is strictly for convenience, to be minimally - // forward-compatible. This will definitely be removed in the - // short term, as I do not expect the need to install an older - // version of uBO to ever be needed beyond the short term. - // >>>>>>>> - filterLists: µb.oldDataFromNewListKeys(µb.selectedFilterLists) - // <<<<<<<< + userFilters: '' }; var onUserFiltersReady = function(details) { @@ -760,17 +775,8 @@ var restoreUserData = function(request) { lastBackupTime: 0 }); µb.assets.put(µb.userFiltersPath, userData.userFilters); - - // 'filterLists' is available up to uBO v1.10.4, not beyond. - // 'selectedFilterLists' is available from uBO v1.11 and beyond. - var listKeys; if ( Array.isArray(userData.selectedFilterLists) ) { - listKeys = userData.selectedFilterLists; - } else if ( userData.filterLists instanceof Object ) { - listKeys = µb.newListKeysFromOldData(userData.filterLists); - } - if ( listKeys !== undefined ) { - µb.saveSelectedFilterLists(listKeys, restart); + µb.saveSelectedFilterLists(userData.selectedFilterLists, restart); } else { restart(); } @@ -828,8 +834,7 @@ var getLists = function(callback) { ignoreGenericCosmeticFilters: µb.userSettings.ignoreGenericCosmeticFilters, netFilterCount: µb.staticNetFilteringEngine.getFilterCount(), parseCosmeticFilters: µb.userSettings.parseAllABPHideFilters, - userFiltersPath: µb.userFiltersPath, - aliases: µb.assets.listKeyAliases + userFiltersPath: µb.userFiltersPath }; var onMetadataReady = function(entries) { r.cache = entries; @@ -1005,7 +1010,32 @@ vAPI.messaging.listen('dashboard', onMessage); /******************************************************************************/ -var µb = µBlock; +var µb = µBlock, + extensionPageURL = vAPI.getURL(''); + +/******************************************************************************/ + +var getLoggerData = function(ownerId, activeTabId, callback) { + var tabIds = {}; + for ( var tabId in µb.pageStores ) { + var pageStore = µb.pageStoreFromTabId(tabId); + if ( pageStore === null ) { continue; } + if ( pageStore.rawURL.startsWith(extensionPageURL) ) { continue; } + tabIds[tabId] = pageStore.title; + } + if ( activeTabId && tabIds.hasOwnProperty(activeTabId) === false ) { + activeTabId = undefined; + } + callback({ + colorBlind: µb.userSettings.colorBlindFriendly, + entries: µb.logger.readAll(ownerId), + maxEntries: µb.userSettings.requestLogMaxEntries, + noTabId: vAPI.noTabId, + activeTabId: activeTabId, + tabIds: tabIds, + tabIdsToken: µb.pageStoresToken + }); +}; /******************************************************************************/ @@ -1043,6 +1073,23 @@ var getURLFilteringData = function(details) { var onMessage = function(request, sender, callback) { // Async switch ( request.what ) { + case 'readAll': + if ( + µb.logger.ownerId !== undefined && + µb.logger.ownerId !== request.ownerId + ) { + callback({ unavailable: true }); + return; + } + vAPI.tabs.get(null, function(tab) { + getLoggerData( + request.ownerId, + tab && tab.id.toString(), + callback + ); + }); + return; + default: break; } @@ -1051,27 +1098,10 @@ var onMessage = function(request, sender, callback) { var response; switch ( request.what ) { - case 'readAll': - var tabIds = {}, pageStore; - var loggerURL = vAPI.getURL('logger-ui.html'); - for ( var tabId in µb.pageStores ) { - pageStore = µb.pageStoreFromTabId(tabId); - if ( pageStore === null ) { - continue; - } - if ( pageStore.rawURL.startsWith(loggerURL) ) { - continue; - } - tabIds[tabId] = pageStore.title; + case 'releaseView': + if ( request.ownerId === µb.logger.ownerId ) { + µb.logger.ownerId = undefined; } - response = { - colorBlind: µb.userSettings.colorBlindFriendly, - entries: µb.logger.readAll(), - maxEntries: µb.userSettings.requestLogMaxEntries, - noTabId: vAPI.noTabId, - tabIds: tabIds, - tabIdsToken: µb.pageStoresToken - }; break; case 'saveURLFilteringRules': @@ -1107,8 +1137,6 @@ vAPI.messaging.listen('loggerUI', onMessage); })(); -// https://www.youtube.com/watch?v=3_WcygKJP1k - /******************************************************************************/ /******************************************************************************/ diff --git a/src/js/popup.js b/src/js/popup.js index 457a6c502b386..dfd38d2d004ea 100644 --- a/src/js/popup.js +++ b/src/js/popup.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2017 Raymond Hill + Copyright (C) 2014-2018 Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -831,13 +831,14 @@ var setFirewallRuleHandler = function(ev) { /******************************************************************************/ -var reloadTab = function() { +var reloadTab = function(ev) { messaging.send( 'popupPanel', { what: 'reloadTab', tabId: popupData.tabId, - select: true + select: true, + bypassCache: ev.ctrlKey || ev.metaKey || ev.shiftKey } ); diff --git a/src/js/redirect-engine.js b/src/js/redirect-engine.js index 70912195a5801..47efae2a1f402 100644 --- a/src/js/redirect-engine.js +++ b/src/js/redirect-engine.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2015-2016 Raymond Hill + Copyright (C) 2015-2017 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 @@ -95,6 +95,7 @@ RedirectEngine.prototype.reset = function() { this.ruleTypes = new Set(); this.ruleSources = new Set(); this.ruleDestinations = new Set(); + this.modifyTime = Date.now(); }; /******************************************************************************/ @@ -189,6 +190,7 @@ RedirectEngine.prototype.addRule = function(src, des, type, pattern, redirect) { entries = this.rules.get(key); if ( entries === undefined ) { this.rules.set(key, [ { tok: redirect, pat: pattern } ]); + this.modifyTime = Date.now(); return; } var entry; @@ -363,6 +365,7 @@ RedirectEngine.prototype.fromSelfie = function(selfie) { this.ruleTypes = new Set(selfie.ruleTypes); this.ruleSources = new Set(selfie.ruleSources); this.ruleDestinations = new Set(selfie.ruleDestinations); + this.modifyTime = Date.now(); return true; }; diff --git a/src/js/reverselookup-worker.js b/src/js/reverselookup-worker.js index bb3a01a3b78e9..2073f9b8cebef 100644 --- a/src/js/reverselookup-worker.js +++ b/src/js/reverselookup-worker.js @@ -26,7 +26,26 @@ /******************************************************************************/ var listEntries = Object.create(null), - filterClassSeparator = '\n/* end of network - start of cosmetic */\n'; + reBlockStart = /^#block-start-(\d+)\n/gm; + +/******************************************************************************/ + +var extractBlocks = function(content, begId, endId) { + reBlockStart.lastIndex = 0; + var out = []; + var match = reBlockStart.exec(content); + while ( match !== null ) { + var beg = match.index + match[0].length; + var blockId = parseInt(match[1], 10); + if ( blockId >= begId && blockId < endId ) { + var 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'); +}; /******************************************************************************/ @@ -34,13 +53,11 @@ var fromNetFilter = function(details) { var lists = [], compiledFilter = details.compiledFilter, entry, content, pos, notFound; + for ( var assetKey in listEntries ) { entry = listEntries[assetKey]; if ( entry === undefined ) { continue; } - content = entry.content.slice( - 0, - entry.content.indexOf(filterClassSeparator) - ); + content = extractBlocks(entry.content, 0, 1000); pos = 0; for (;;) { pos = content.indexOf(compiledFilter, pos); @@ -96,7 +113,7 @@ var fromNetFilter = function(details) { // the various compiled versions. var fromCosmeticFilter = function(details) { - var match = /^#@?#/.exec(details.rawFilter), + var match = /^#@?#\^?/.exec(details.rawFilter), prefix = match[0], selector = details.rawFilter.slice(prefix.length); @@ -138,15 +155,14 @@ var fromCosmeticFilter = function(details) { } var response = Object.create(null), - assetKey, entry, content, found, beg, end, fargs; + assetKey, entry, content, + found, beg, end, + fargs, isProcedural; for ( assetKey in listEntries ) { entry = listEntries[assetKey]; if ( entry === undefined ) { continue; } - content = entry.content.slice( - entry.content.indexOf(filterClassSeparator) + - filterClassSeparator.length - ); + content = extractBlocks(entry.content, 1000, 2000); found = undefined; while ( (match = reNeedle.exec(content)) !== null ) { beg = content.lastIndexOf('\n', match.index); @@ -194,12 +210,15 @@ var fromCosmeticFilter = function(details) { found = prefix + selector; } break; - case 6: case 8: case 9: + case 32: + case 64: + case 65: + isProcedural = fargs[3].charCodeAt(0) === 0x7B; if ( - fargs[0] === 8 && fargs[3] !== selector || - fargs[0] === 9 && JSON.parse(fargs[3]).raw !== selector + isProcedural === false && fargs[3] !== selector || + isProcedural && JSON.parse(fargs[3]).raw !== selector ) { break; } diff --git a/src/js/rpcreceiver.js b/src/js/rpcreceiver.js index 681d0f7d017ef..86adbcb5960cf 100644 --- a/src/js/rpcreceiver.js +++ b/src/js/rpcreceiver.js @@ -35,22 +35,21 @@ if ( typeof vAPI.rpcReceiver !== 'object' ) { vAPI.rpcReceiver.getScriptTagHostnames = function() { var µb = µBlock; - var cfe = µb.cosmeticFilteringEngine; - if ( !cfe ) { return; } - return cfe.retrieveScriptTagHostnames(); + if ( µb.htmlFilteringEngine ) { + return µb.htmlFilteringEngine.retrieveScriptTagHostnames(); + } }; /******************************************************************************/ vAPI.rpcReceiver.getScriptTagFilters = function(details) { var µb = µBlock; - var cfe = µb.cosmeticFilteringEngine; - if ( !cfe ) { return; } + if ( !µb.htmlFilteringEngine ) { return; } // Fetching the script tag filters first: assuming it is faster than // checking whether the site is whitelisted. var hostname = details.frameHostname; - var r = cfe.retrieveScriptTagRegex( - µb.URI.domainFromHostname(hostname) || hostname, + var r = µb.htmlFilteringEngine.retrieveScriptTagRegex( + µb.URI.domainFromHostname(hostname), hostname ); // https://github.com/gorhill/uBlock/issues/838 diff --git a/src/js/scriptlet-filtering.js b/src/js/scriptlet-filtering.js new file mode 100644 index 0000000000000..3ace313ff4853 --- /dev/null +++ b/src/js/scriptlet-filtering.js @@ -0,0 +1,291 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2017 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'; + +/******************************************************************************/ + +µBlock.scriptletFilteringEngine = (function() { + var api = {}; + + var µb = µBlock, + scriptletDB = new µb.staticExtFilteringEngine.HostnameBasedDB(), + duplicates = new Set(), + acceptedCount = 0, + discardedCount = 0, + scriptletCache = new µb.MRUCache(32), + exceptionsRegister = new Set(), + scriptletsRegister = new Map(), + reEscapeScriptArg = /[\\'"]/g; + + var scriptletRemover = [ + '(function() {', + ' var c = document.currentScript, p = c && c.parentNode;', + ' if ( p ) { p.removeChild(c); }', + '})();' + ].join('\n'); + + + var lookupScriptlet = function(raw, reng, toInject) { + if ( toInject.has(raw) ) { return; } + if ( scriptletCache.resetTime < reng.modifyTime ) { + scriptletCache.reset(); + } + var content = scriptletCache.lookup(raw); + if ( content === undefined ) { + var token, args, + pos = raw.indexOf(','); + if ( pos === -1 ) { + token = raw; + } else { + token = raw.slice(0, pos).trim(); + args = raw.slice(pos + 1).trim(); + } + content = reng.resourceContentFromName(token, 'application/javascript'); + if ( !content ) { return; } + if ( args ) { + content = patchScriptlet(content, args); + if ( !content ) { return; } + } + scriptletCache.add(raw, content); + } + toInject.set(raw, content); + }; + + // Fill template placeholders. Return falsy if: + // - At least one argument contains anything else than /\w/ and `.` + + var patchScriptlet = function(content, args) { + var i = 1, + pos, arg; + while ( args !== '' ) { + pos = args.indexOf(','); + if ( pos === -1 ) { pos = args.length; } + arg = args.slice(0, pos).trim().replace(reEscapeScriptArg, '\\$&'); + content = content.replace('{{' + i + '}}', arg); + args = args.slice(pos + 1).trim(); + i++; + } + return content; + }; + + var logOne = function(isException, token, details) { + µb.logger.writeOne( + details.tabId, + 'cosmetic', + { + source: 'cosmetic', + raw: (isException ? '#@#' : '##') + 'script:inject(' + token + ')' + }, + 'dom', + details.url, + null, + details.hostname + ); + }; + + api.reset = function() { + scriptletDB.clear(); + duplicates.clear(); + acceptedCount = 0; + discardedCount = 0; + }; + + api.freeze = function() { + duplicates.clear(); + }; + + api.compile = function(parsed, writer) { + // 1001 = scriptlet injection + writer.select(1001); + + // Only exception filters are allowed to be global. + + if ( parsed.hostnames.length === 0 ) { + if ( parsed.exception ) { + writer.push([ 32, '!', '', parsed.suffix ]); + } + return; + } + + // https://github.com/gorhill/uBlock/issues/3375 + // Ignore instances of exception filter with negated hostnames, + // because there is no way to create an exception to an exception. + + var µburi = µb.URI; + + for ( var hostname of parsed.hostnames ) { + var negated = hostname.charCodeAt(0) === 0x7E /* '~' */; + if ( negated ) { + hostname = hostname.slice(1); + } + var hash = µburi.domainFromHostname(hostname); + if ( parsed.exception ) { + if ( negated ) { continue; } + hash = '!' + hash; + } else if ( negated ) { + hash = '!' + hash; + } + writer.push([ 32, hash, hostname, parsed.suffix ]); + } + }; + + // 01234567890123456789 + // script:inject(token[, arg[, ...]]) + // ^ ^ + // 14 -1 + + api.fromCompiledContent = function(reader) { + // 1001 = scriptlet injection + reader.select(1001); + + while ( reader.next() ) { + acceptedCount += 1; + var fingerprint = reader.fingerprint(); + if ( duplicates.has(fingerprint) ) { + discardedCount += 1; + continue; + } + duplicates.add(fingerprint); + var args = reader.args(); + if ( args.length < 4 ) { continue; } + scriptletDB.add( + args[1], + { hostname: args[2], token: args[3].slice(14, -1) } + ); + } + }; + + api.retrieve = function(request) { + if ( scriptletDB.size === 0 ) { return; } + if ( µb.hiddenSettings.ignoreScriptInjectFilters ) { return; } + + var reng = µb.redirectEngine; + if ( !reng ) { return; } + + var hostname = request.hostname; + + // https://github.com/gorhill/uBlock/issues/2835 + // Do not inject scriptlets if the site is under an `allow` rule. + if ( + µb.userSettings.advancedUserEnabled && + µb.sessionFirewall.evaluateCellZY(hostname, hostname, '*') === 2 + ) { + return; + } + + var domain = request.domain, + entity = request.entity, + entries, entry; + + // https://github.com/gorhill/uBlock/issues/1954 + // Implicit + var hn = hostname; + for (;;) { + lookupScriptlet(hn + '.js', reng, scriptletsRegister); + if ( hn === domain ) { break; } + var pos = hn.indexOf('.'); + if ( pos === -1 ) { break; } + hn = hn.slice(pos + 1); + } + if ( entity !== '' ) { + lookupScriptlet(entity + '.js', reng, scriptletsRegister); + } + + // Explicit + entries = []; + if ( domain !== '' ) { + scriptletDB.retrieve(domain, hostname, entries); + scriptletDB.retrieve(entity, entity, entries); + } + scriptletDB.retrieve('', hostname, entries); + for ( entry of entries ) { + lookupScriptlet(entry.token, reng, scriptletsRegister); + } + + if ( scriptletsRegister.size === 0 ) { return; } + + // Collect exception filters. + entries = []; + if ( domain !== '' ) { + scriptletDB.retrieve('!' + domain, hostname, entries); + scriptletDB.retrieve('!' + entity, entity, entries); + } + scriptletDB.retrieve('!', hostname, entries); + for ( entry of entries ) { + exceptionsRegister.add(entry.token); + } + + // Return an array of scriptlets, and log results if needed. + var out = [], + logger = µb.logger.isEnabled() ? µb.logger : null, + isException; + for ( entry of scriptletsRegister ) { + if ( (isException = exceptionsRegister.has(entry[0])) === false ) { + out.push(entry[1]); + } + if ( logger !== null ) { + logOne(isException, entry[0], request); + } + } + + scriptletsRegister.clear(); + exceptionsRegister.clear(); + + if ( out.length === 0 ) { return; } + + out.push(scriptletRemover); + + return out.join('\n'); + }; + + api.apply = function(doc, details) { + var script = doc.createElement('script'); + script.textContent = details.scriptlets; + doc.head.insertBefore(script, doc.head.firstChild); + return true; + }; + + api.toSelfie = function() { + return scriptletDB.toSelfie(); + }; + + api.fromSelfie = function(selfie) { + scriptletDB = new µb.staticExtFilteringEngine.HostnameBasedDB(selfie); + }; + + Object.defineProperties(api, { + acceptedCount: { + get: function() { + return acceptedCount; + } + }, + discardedCount: { + get: function() { + return discardedCount; + } + } + }); + + return api; +})(); + +/******************************************************************************/ diff --git a/src/js/scriptlets/dom-inspector.js b/src/js/scriptlets/dom-inspector.js index ec02f8f072f13..5b2f4df73f938 100644 --- a/src/js/scriptlets/dom-inspector.js +++ b/src/js/scriptlets/dom-inspector.js @@ -589,7 +589,12 @@ var elementsFromSpecialSelector = function(selector) { var out = [], i; var matches = /^(.+?):has\((.+?)\)$/.exec(selector); if ( matches !== null ) { - var nodes = document.querySelectorAll(matches[1]); + var nodes; + try { + nodes = document.querySelectorAll(matches[1]); + } catch(ex) { + nodes = []; + } i = nodes.length; while ( i-- ) { var node = nodes[i]; diff --git a/src/js/start.js b/src/js/start.js index f28280cf735a0..57f340812c282 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -39,7 +39,7 @@ vAPI.app.onShutdown = function() { µb.staticFilteringReverseLookup.shutdown(); µb.assets.updateStop(); µb.staticNetFilteringEngine.reset(); - µb.cosmeticFilteringEngine.reset(); + µb.staticExtFilteringEngine.reset(); µb.sessionFirewall.reset(); µb.permanentFirewall.reset(); µb.permanentFirewall.reset(); @@ -139,7 +139,7 @@ var onSelfieReady = function(selfie) { µb.availableFilterLists = selfie.availableFilterLists; µb.staticNetFilteringEngine.fromSelfie(selfie.staticNetFilteringEngine); µb.redirectEngine.fromSelfie(selfie.redirectEngine); - µb.cosmeticFilteringEngine.fromSelfie(selfie.cosmeticFilteringEngine); + µb.staticExtFilteringEngine.fromSelfie(selfie.staticExtFilteringEngine); return true; }; diff --git a/src/js/static-ext-filtering.js b/src/js/static-ext-filtering.js new file mode 100644 index 0000000000000..6d90a03b80a16 --- /dev/null +++ b/src/js/static-ext-filtering.js @@ -0,0 +1,712 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2017 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 punycode */ + +'use strict'; + +/******************************************************************************* + + All static extended filters are of the form: + + field 1: one hostname, or a list of comma-separated hostnames + field 2: `##` or `#@#` + field 3: selector + + The purpose of the static extended filtering engine is to coarse-parse and + dispatch to appropriate specialized filtering engines. There are currently + three specialized filtering engines: + + - cosmetic filtering (aka "element hiding" in Adblock Plus) + - scriptlet injection: selector starts with `script:inject` + - html filtering: selector starts with `^` + + Depending on the specialized filtering engine, field 1 may or may not be + optional. + + The static extended filtering engine also offers parsing capabilities which + are available to all other specialized fitlering engines. For example, + cosmetic and html filtering can ask the extended filtering engine to + compile/validate selectors. + +**/ + +µBlock.staticExtFilteringEngine = (function() { + var µb = µBlock, + reHostnameSeparator = /\s*,\s*/, + reHasUnicode = /[^\x00-\x7F]/, + reParseRegexLiteral = /^\/(.+)\/([im]+)?$/, + emptyArray = [], + parsed = { + hostnames: [], + exception: false, + suffix: '' + }; + + var isValidCSSSelector = (function() { + var div = document.createElement('div'), + matchesFn; + // Keep in mind: + // https://github.com/gorhill/uBlock/issues/693 + // https://github.com/gorhill/uBlock/issues/1955 + if ( div.matches instanceof Function ) { + matchesFn = div.matches.bind(div); + } else if ( div.mozMatchesSelector instanceof Function ) { + matchesFn = div.mozMatchesSelector.bind(div); + } else if ( div.webkitMatchesSelector instanceof Function ) { + matchesFn = div.webkitMatchesSelector.bind(div); + } else if ( div.msMatchesSelector instanceof Function ) { + matchesFn = div.msMatchesSelector.bind(div); + } else { + matchesFn = div.querySelector.bind(div); + } + // https://github.com/gorhill/uBlock/issues/3111 + // Workaround until https://bugzilla.mozilla.org/show_bug.cgi?id=1406817 + // is fixed. + try { + matchesFn(':scope'); + } catch (ex) { + matchesFn = div.querySelector.bind(div); + } + return function(s) { + try { + matchesFn(s + ', ' + s + ':not(#foo)'); + } catch (ex) { + return false; + } + return true; + }; + })(); + + + var isBadRegex = function(s) { + try { + void new RegExp(s); + } catch (ex) { + isBadRegex.message = ex.toString(); + return true; + } + return false; + }; + + var translateAdguardCSSInjectionFilter = function(suffix) { + var matches = /^([^{]+)\{([^}]+)\}$/.exec(suffix); + if ( matches === null ) { return ''; } + return matches[1].trim() + ':style(' + matches[2].trim() + ')'; + }; + + var toASCIIHostnames = function(hostnames) { + var i = hostnames.length; + while ( i-- ) { + var hostname = hostnames[i]; + hostnames[i] = hostname.charCodeAt(0) === 0x7E /* '~' */ ? + '~' + punycode.toASCII(hostname.slice(1)) : + punycode.toASCII(hostname); + } + }; + + var compileProceduralSelector = (function() { + var reProceduralOperator = new RegExp([ + '^(?:', + [ + '-abp-contains', + '-abp-has', + 'contains', + 'has', + 'has-text', + 'if', + 'if-not', + 'matches-css', + 'matches-css-after', + 'matches-css-before', + 'xpath' + ].join('|'), + ')\\(' + ].join('')); + + var reEscapeRegex = /[.*+?^${}()|[\]\\]/g, + reNeedScope = /^\s*[+>~]/, + reIsDanglingSelector = /(?:[+>~]\s*|\s+)$/; + + var lastProceduralSelector = '', + lastProceduralSelectorCompiled, + regexToRawValue = new Map(); + + var compileText = function(s) { + var regexDetails, + match = reParseRegexLiteral.exec(s); + if ( match !== null ) { + regexDetails = match[1]; + if ( isBadRegex(regexDetails) ) { return; } + if ( match[2] ) { + regexDetails = [ regexDetails, match[2] ]; + } + } else { + regexDetails = s.replace(reEscapeRegex, '\\$&'); + regexToRawValue.set(regexDetails, s); + } + return regexDetails; + }; + + var compileCSSDeclaration = function(s) { + var name, value, regexDetails, + pos = s.indexOf(':'); + if ( pos === -1 ) { return; } + name = s.slice(0, pos).trim(); + value = s.slice(pos + 1).trim(); + var match = reParseRegexLiteral.exec(value); + if ( match !== null ) { + regexDetails = match[1]; + if ( isBadRegex(regexDetails) ) { return; } + if ( match[2] ) { + regexDetails = [ regexDetails, match[2] ]; + } + } else { + regexDetails = '^' + value.replace(reEscapeRegex, '\\$&') + '$'; + regexToRawValue.set(regexDetails, value); + } + return { name: name, value: regexDetails }; + }; + + var compileConditionalSelector = function(s) { + // https://github.com/AdguardTeam/ExtendedCss/issues/31#issuecomment-302391277 + // Prepend `:scope ` if needed. + if ( reNeedScope.test(s) ) { + s = ':scope ' + s; + } + return compile(s); + }; + + var compileXpathExpression = function(s) { + try { + document.createExpression(s, null); + } catch (e) { + return; + } + return s; + }; + + // https://github.com/gorhill/uBlock/issues/2793 + var normalizedOperators = new Map([ + [ ':-abp-contains', ':has-text' ], + [ ':-abp-has', ':if' ], + [ ':contains', ':has-text' ] + ]); + + var compileArgument = new Map([ + [ ':has', compileConditionalSelector ], + [ ':has-text', compileText ], + [ ':if', compileConditionalSelector ], + [ ':if-not', compileConditionalSelector ], + [ ':matches-css', compileCSSDeclaration ], + [ ':matches-css-after', compileCSSDeclaration ], + [ ':matches-css-before', compileCSSDeclaration ], + [ ':xpath', compileXpathExpression ] + ]); + + // https://github.com/gorhill/uBlock/issues/2793#issuecomment-333269387 + // Normalize (somewhat) the stringified version of procedural + // cosmetic filters -- this increase the likelihood of detecting + // duplicates given that uBO is able to understand syntax specific + // to other blockers. + // The normalized string version is what is reported in the logger, + // by design. + var decompile = function(compiled) { + var tasks = compiled.tasks; + if ( Array.isArray(tasks) === false ) { + return compiled.selector; + } + var raw = [ compiled.selector ], + value; + for ( var i = 0, n = tasks.length, task; i < n; i++ ) { + task = tasks[i]; + switch ( task[0] ) { + case ':xpath': + raw.push(task[0], '(', task[1], ')'); + break; + case ':has-text': + if ( Array.isArray(task[1]) ) { + value = '/' + task[1][0] + '/' + task[1][1]; + } else { + value = regexToRawValue.get(task[1]); + if ( value === undefined ) { + value = '/' + task[1] + '/'; + } + } + raw.push(task[0], '(', value, ')'); + break; + case ':matches-css': + case ':matches-css-after': + case ':matches-css-before': + if ( Array.isArray(task[1].value) ) { + value = '/' + task[1].value[0] + '/' + task[1].value[1]; + } else { + value = regexToRawValue.get(task[1].value); + if ( value === undefined ) { + value = '/' + task[1].value + '/'; + } + } + raw.push(task[0], '(', task[1].name, ': ', value, ')'); + break; + case ':has': + case ':if': + case ':if-not': + raw.push(task[0], '(', decompile(task[1]), ')'); + break; + } + } + return raw.join(''); + }; + + var compile = function(raw) { + if ( raw === '' ) { return; } + var prefix = '', + tasks = []; + for (;;) { + var i = 0, + n = raw.length, + c, match; + // Advance to next operator. + while ( i < n ) { + c = raw.charCodeAt(i++); + if ( c === 0x3A /* ':' */ ) { + match = reProceduralOperator.exec(raw.slice(i)); + if ( match !== null ) { break; } + } + } + if ( i === n ) { break; } + var opNameBeg = i - 1; + var opNameEnd = i + match[0].length - 1; + i += match[0].length; + // Find end of argument: first balanced closing parenthesis. + // Note: unbalanced parenthesis can be used in a regex literal + // when they are escaped using `\`. + var pcnt = 1; + while ( i < n ) { + c = raw.charCodeAt(i++); + if ( c === 0x5C /* '\\' */ ) { + if ( i < n ) { i += 1; } + } else if ( c === 0x28 /* '(' */ ) { + pcnt +=1 ; + } else if ( c === 0x29 /* ')' */ ) { + pcnt -= 1; + if ( pcnt === 0 ) { break; } + } + } + // Unbalanced parenthesis? + if ( pcnt !== 0 ) { return; } + // Extract and remember operator details. + var operator = raw.slice(opNameBeg, opNameEnd); + operator = normalizedOperators.get(operator) || operator; + var args = raw.slice(opNameEnd + 1, i - 1); + args = compileArgument.get(operator)(args); + if ( args === undefined ) { return; } + if ( tasks.length === 0 ) { + prefix = raw.slice(0, opNameBeg); + } else if ( opNameBeg !== 0 ) { + return; + } + tasks.push([ operator, args ]); + raw = raw.slice(i); + if ( i === n ) { break; } + } + // No task found: then we have a CSS selector. + // At least one task found: nothing should be left to parse. + if ( tasks.length === 0 ) { + prefix = raw; + tasks = undefined; + } else if ( raw.length !== 0 ) { + return; + } + // https://github.com/NanoAdblocker/NanoCore/issues/1#issuecomment-354394894 + if ( prefix !== '' ) { + if ( reIsDanglingSelector.test(prefix) ) { prefix += '*'; } + if ( isValidCSSSelector(prefix) === false ) { return; } + } + return { selector: prefix, tasks: tasks }; + }; + + var entryPoint = function(raw) { + if ( raw === lastProceduralSelector ) { + return lastProceduralSelectorCompiled; + } + lastProceduralSelector = raw; + var compiled = compile(raw); + if ( compiled !== undefined ) { + compiled.raw = decompile(compiled); + compiled = JSON.stringify(compiled); + } + lastProceduralSelectorCompiled = compiled; + return compiled; + }; + + entryPoint.reset = function() { + regexToRawValue = new Map(); + lastProceduralSelector = ''; + lastProceduralSelectorCompiled = undefined; + }; + + return entryPoint; + })(); + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + var api = {}; + + //-------------------------------------------------------------------------- + // Public classes + //-------------------------------------------------------------------------- + + api.HostnameBasedDB = function(selfie) { + if ( selfie !== undefined ) { + this.db = new Map(selfie.map); + this.size = selfie.size; + } else { + this.db = new Map(); + this.size = 0; + } + }; + + api.HostnameBasedDB.prototype = { + add: function(hash, entry) { + var bucket = this.db.get(hash); + if ( bucket === undefined ) { + this.db.set(hash, entry); + } else if ( Array.isArray(bucket) ) { + bucket.push(entry); + } else { + this.db.set(hash, [ bucket, entry ]); + } + this.size += 1; + }, + clear: function() { + this.db.clear(); + this.size = 0; + }, + retrieve: function(hash, hostname, out) { + var bucket = this.db.get(hash); + if ( bucket === undefined ) { return; } + if ( Array.isArray(bucket) === false ) { + if ( hostname.endsWith(bucket.hostname) ) { out.push(bucket); } + return; + } + var i = bucket.length; + while ( i-- ) { + var entry = bucket[i]; + if ( hostname.endsWith(entry.hostname) ) { out.push(entry); } + } + }, + toSelfie: function() { + return { + map: Array.from(this.db), + size: this.size + }; + } + }; + + api.HostnameBasedDB.prototype[Symbol.iterator] = (function() { + var Iter = function(db) { + this.mapIter = db.values(); + this.arrayIter = undefined; + }; + Iter.prototype.next = function() { + var result; + if ( this.arrayIter !== undefined ) { + result = this.arrayIter.next(); + if ( result.done === false ) { return result; } + this.arrayIter = undefined; + } + result = this.mapIter.next(); + if ( result.done || Array.isArray(result.value) === false ) { + return result; + } + this.arrayIter = result.value[Symbol.iterator](); + return this.arrayIter.next(); // array should never be empty + }; + return function() { + return new Iter(this.db); + }; + })(); + + //-------------------------------------------------------------------------- + // Public methods + //-------------------------------------------------------------------------- + + api.reset = function() { + compileProceduralSelector.reset(); + µb.cosmeticFilteringEngine.reset(); + µb.scriptletFilteringEngine.reset(); + µb.htmlFilteringEngine.reset(); + }; + + api.freeze = function() { + compileProceduralSelector.reset(); + µb.cosmeticFilteringEngine.freeze(); + µb.scriptletFilteringEngine.freeze(); + µb.htmlFilteringEngine.freeze(); + }; + + // https://github.com/chrisaljoudi/uBlock/issues/1004 + // Detect and report invalid CSS selectors. + + // Discard new ABP's `-abp-properties` directive until it is + // implemented (if ever). Unlikely, see: + // https://github.com/gorhill/uBlock/issues/1752 + + // https://github.com/gorhill/uBlock/issues/2624 + // Convert Adguard's `-ext-has='...'` into uBO's `:has(...)`. + + api.compileSelector = (function() { + var reAfterBeforeSelector = /^(.+?)(::?after|::?before)$/, + reStyleSelector = /^(.+?):style\((.+?)\)$/, + reStyleBad = /url\([^)]+\)/, + reExtendedSyntax = /\[-(?:abp|ext)-[a-z-]+=(['"])(?:.+?)(?:\1)\]/, + reExtendedSyntaxParser = /\[-(?:abp|ext)-([a-z-]+)=(['"])(.+?)\2\]/, + div = document.createElement('div'); + + var normalizedExtendedSyntaxOperators = new Map([ + [ 'contains', ':has-text' ], + [ 'has', ':if' ], + [ 'matches-css', ':matches-css' ], + [ 'matches-css-after', ':matches-css-after' ], + [ 'matches-css-before', ':matches-css-before' ], + ]); + + var isValidStyleProperty = function(cssText) { + if ( reStyleBad.test(cssText) ) { return false; } + div.style.cssText = cssText; + if ( div.style.cssText === '' ) { return false; } + div.style.cssText = ''; + return true; + }; + + var entryPoint = function(raw) { + var extendedSyntax = reExtendedSyntax.test(raw); + if ( isValidCSSSelector(raw) && extendedSyntax === false ) { + return raw; + } + + // We rarely reach this point -- majority of selectors are plain + // CSS selectors. + + var matches, operator; + + // Supported Adguard/ABP advanced selector syntax: will translate into + // uBO's syntax before further processing. + // Mind unsupported advanced selector syntax, such as ABP's + // `-abp-properties`. + // Note: extended selector syntax has been deprecated in ABP, in favor + // of the procedural one (i.e. `:operator(...)`). See + // https://issues.adblockplus.org/ticket/5287 + if ( extendedSyntax ) { + while ( (matches = reExtendedSyntaxParser.exec(raw)) !== null ) { + operator = normalizedExtendedSyntaxOperators.get(matches[1]); + if ( operator === undefined ) { return; } + raw = raw.slice(0, matches.index) + + operator + '(' + matches[3] + ')' + + raw.slice(matches.index + matches[0].length); + } + return entryPoint(raw); + } + + var selector = raw, + pseudoclass, style; + + // `:style` selector? + if ( (matches = reStyleSelector.exec(selector)) !== null ) { + selector = matches[1]; + style = matches[2]; + } + + // https://github.com/gorhill/uBlock/issues/2448 + // :after- or :before-based selector? + if ( (matches = reAfterBeforeSelector.exec(selector)) ) { + selector = matches[1]; + pseudoclass = matches[2]; + } + + if ( style !== undefined || pseudoclass !== undefined ) { + if ( isValidCSSSelector(selector) === false ) { + return; + } + if ( pseudoclass !== undefined ) { + selector += pseudoclass; + } + if ( style !== undefined ) { + if ( isValidStyleProperty(style) === false ) { return; } + return JSON.stringify({ + raw: raw, + style: [ selector, style ] + }); + } + return JSON.stringify({ + raw: raw, + pseudoclass: true + }); + } + + // Procedural selector? + var compiled; + if ( (compiled = compileProceduralSelector(raw)) ) { + return compiled; + } + + µb.logger.writeOne( + '', + 'error', + 'Cosmetic filtering – invalid filter: ' + raw + ); + }; + + return entryPoint; + })(); + + api.compile = function(raw, writer) { + var lpos = raw.indexOf('#'); + if ( lpos === -1 ) { return false; } + var rpos = lpos + 1; + if ( raw.charCodeAt(rpos) !== 0x23 /* '#' */ ) { + rpos = raw.indexOf('#', rpos + 1); + if ( rpos === -1 ) { return false; } + } + + // Coarse-check that the anchor is valid. + // `##`: l = 1 + // `#@#`, `#$#`, `#%#`, `#?#`: l = 2 + // `#@$#`, `#@%#`, `#@?#`: l = 3 + if ( (rpos - lpos) > 3 ) { return false; } + + // Extract the selector. + var suffix = raw.slice(rpos + 1).trim(); + if ( suffix.length === 0 ) { return false; } + parsed.suffix = suffix; + + // https://github.com/gorhill/uBlock/issues/952 + // Find out whether we are dealing with an Adguard-specific cosmetic + // filter, and if so, translate it if supported, or discard it if not + // supported. + // We have an Adguard/ABP cosmetic filter if and only if the + // character is `$`, `%` or `?`, otherwise it's not a cosmetic + // filter. + var cCode = raw.charCodeAt(rpos - 1); + if ( cCode !== 0x23 /* '#' */ && cCode !== 0x40 /* '@' */ ) { + // Adguard's scriptlet injection: not supported. + if ( cCode === 0x25 /* '%' */ ) { return true; } + // Not a known extended filter. + if ( cCode !== 0x24 /* '$' */ && cCode !== 0x3F /* '?' */ ) { + return false; + } + // Adguard's style injection: translate to uBO's format. + if ( cCode === 0x24 /* '$' */ ) { + suffix = translateAdguardCSSInjectionFilter(suffix); + if ( suffix === '' ) { return true; } + parsed.suffix = suffix; + } + } + + // Exception filter? + parsed.exception = raw.charCodeAt(lpos + 1) === 0x40 /* '@' */; + + // Extract the hostname(s), punycode if required. + if ( lpos === 0 ) { + parsed.hostnames = emptyArray; + } else { + var prefix = raw.slice(0, lpos); + parsed.hostnames = prefix.split(reHostnameSeparator); + if ( reHasUnicode.test(prefix) ) { + toASCIIHostnames(parsed.hostnames); + } + } + + if ( suffix.startsWith('script:') ) { + // Scriptlet injection engine. + if ( suffix.startsWith('script:inject') ) { + µb.scriptletFilteringEngine.compile(parsed, writer); + return true; + } + // Script tag filtering: courtesy-conversion to HTML filtering. + if ( suffix.startsWith('script:contains') ) { + console.info( + 'uBO: ##script:contains(...) is deprecated, ' + + 'converting to ##^script:has-text(...)' + ); + suffix = suffix.replace(/^script:contains/, '^script:has-text'); + parsed.suffix = suffix; + } + } + + // HTML filtering engine. + // TODO: evaluate converting Adguard's `$$` syntax into uBO's HTML + // filtering syntax. + if ( suffix.charCodeAt(0) === 0x5E /* '^' */ ) { + µb.htmlFilteringEngine.compile(parsed, writer); + return true; + } + + // Cosmetic filtering engine. + µb.cosmeticFilteringEngine.compile(parsed, writer); + return true; + }; + + api.fromCompiledContent = function(reader, options) { + µb.cosmeticFilteringEngine.fromCompiledContent(reader, options); + µb.scriptletFilteringEngine.fromCompiledContent(reader, options); + µb.htmlFilteringEngine.fromCompiledContent(reader, options); + }; + + api.toSelfie = function() { + return { + cosmetic: µb.cosmeticFilteringEngine.toSelfie(), + scriptlets: µb.scriptletFilteringEngine.toSelfie(), + html: µb.htmlFilteringEngine.toSelfie() + + }; + }; + + Object.defineProperties(api, { + acceptedCount: { + get: function() { + return µb.cosmeticFilteringEngine.acceptedCount + + µb.scriptletFilteringEngine.acceptedCount + + µb.htmlFilteringEngine.acceptedCount; + } + }, + discardedCount: { + get: function() { + return µb.cosmeticFilteringEngine.discardedCount + + µb.scriptletFilteringEngine.discardedCount + + µb.htmlFilteringEngine.discardedCount; + } + } + }); + + api.fromSelfie = function(selfie) { + µb.cosmeticFilteringEngine.fromSelfie(selfie.cosmetic); + µb.scriptletFilteringEngine.fromSelfie(selfie.scriptlets); + µb.htmlFilteringEngine.fromSelfie(selfie.html); + }; + + return api; +})(); + +/******************************************************************************/ diff --git a/src/js/static-net-filtering.js b/src/js/static-net-filtering.js index 545b8b525d1a2..bf1da6f634c77 100644 --- a/src/js/static-net-filtering.js +++ b/src/js/static-net-filtering.js @@ -1735,7 +1735,7 @@ FilterParser.prototype.parse = function(raw) { // Abort if type is only for unsupported types, otherwise // toggle off `unsupported` bit. if ( this.types & this.unsupportedTypeBit ) { - this.types &= ~(this.unsupportedTypeBit | this.allNetRequestTypeBits); + this.types &= ~this.unsupportedTypeBit; if ( this.types === 0 ) { this.unsupported = true; return this; @@ -2116,6 +2116,9 @@ FilterContainer.prototype.compile = function(raw, writer) { return false; } + // 0 = network filters + writer.select(0); + // Pure hostnames, use more efficient dictionary lookup // https://github.com/chrisaljoudi/uBlock/issues/665 // Create a dict keyed on request type etc. @@ -2268,6 +2271,9 @@ FilterContainer.prototype.fromCompiledContent = function(reader) { args, bits, bucket, entry, tokenHash, fdata, fingerprint; + // 0 = network filters + reader.select(0); + while ( reader.next() === true ) { args = reader.args(); bits = args[0]; diff --git a/src/js/storage.js b/src/js/storage.js index a9a5d93746e24..e0968e83ff1d0 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -168,29 +168,21 @@ µBlock.loadSelectedFilterLists = function(callback) { var µb = this; - vAPI.storage.get([ 'selectedFilterLists', 'remoteBlacklists' ], function(bin) { - if ( !bin || !bin.selectedFilterLists && !bin.remoteBlacklists ) { - // Select default filter lists if first-time launch. + vAPI.storage.get('selectedFilterLists', function(bin) { + // Select default filter lists if first-time launch. + if ( !bin || Array.isArray(bin.selectedFilterLists) === false ) { µb.assets.metadata(function(availableLists) { - µb.saveSelectedFilterLists(µb.autoSelectRegionalFilterLists(availableLists)); + µb.saveSelectedFilterLists( + µb.autoSelectRegionalFilterLists(availableLists) + ); callback(); }); return; } - var listKeys = []; - if ( bin.selectedFilterLists ) { - listKeys = bin.selectedFilterLists; - } else if ( bin.remoteBlacklists ) { - var oldListKeys = µb.newListKeysFromOldData(bin.remoteBlacklists); - if ( oldListKeys.sort().join() !== listKeys.sort().join() ) { - listKeys = oldListKeys; - µb.saveSelectedFilterLists(listKeys); - } - // TODO(seamless migration): - // Uncomment when all have moved to v1.11 and beyond. - //vAPI.storage.remove('remoteBlacklists'); - } - µb.selectedFilterLists = listKeys; + // TODO: Removes once 1.1.15 is in widespread use. + // https://github.com/gorhill/uBlock/issues/3383 + vAPI.storage.remove('remoteBlacklists'); + µb.selectedFilterLists = bin.selectedFilterLists; callback(); }); }; @@ -213,63 +205,12 @@ } newKeys = this.arrayFrom(newSet); var bin = { - selectedFilterLists: newKeys, - remoteBlacklists: this.oldDataFromNewListKeys(newKeys) + selectedFilterLists: newKeys }; this.selectedFilterLists = newKeys; vAPI.storage.set(bin, callback); }; -// TODO(seamless migration): -// Remove when all have moved to v1.11 and beyond. -// >>>>>>>> -µBlock.newListKeysFromOldData = function(oldLists) { - var aliases = this.assets.listKeyAliases, - listKeys = [], newKey; - for ( var oldKey in oldLists ) { - if ( oldLists[oldKey].off !== true ) { - newKey = aliases[oldKey]; - listKeys.push(newKey ? newKey : oldKey); - } - } - return listKeys; -}; - -µBlock.oldDataFromNewListKeys = function(selectedFilterLists) { - var µb = this, - remoteBlacklists = {}; - var reverseAliases = Object.keys(this.assets.listKeyAliases).reduce( - function(a, b) { - a[µb.assets.listKeyAliases[b]] = b; return a; - }, - {} - ); - remoteBlacklists = selectedFilterLists.reduce( - function(a, b) { - a[reverseAliases[b] || b] = { off: false }; - return a; - }, - {} - ); - remoteBlacklists = Object.keys(µb.assets.listKeyAliases).reduce( - function(a, b) { - var aliases = µb.assets.listKeyAliases; - if ( - b.startsWith('assets/') && - aliases[b] !== 'public_suffix_list.dat' && - aliases[b] !== 'ublock-resources' && - !a[b] - ) { - a[b] = { off: true }; - } - return a; - }, - remoteBlacklists - ); - return remoteBlacklists; -}; -// <<<<<<<< - /******************************************************************************/ µBlock.applyFilterListSelection = function(details, callback) { @@ -411,7 +352,7 @@ vAPI.storage.set({ 'availableFilterLists': µb.availableFilterLists }); µb.staticNetFilteringEngine.freeze(); µb.redirectEngine.freeze(); - µb.cosmeticFilteringEngine.freeze(); + µb.staticExtFilteringEngine.freeze(); µb.selfieManager.destroy(); }; @@ -607,7 +548,7 @@ var onDone = function() { µb.staticNetFilteringEngine.freeze(); - µb.cosmeticFilteringEngine.freeze(); + µb.staticExtFilteringEngine.freeze(); µb.redirectEngine.freeze(); vAPI.storage.set({ 'availableFilterLists': µb.availableFilterLists }); @@ -626,14 +567,16 @@ var applyCompiledFilters = function(assetKey, compiled) { var snfe = µb.staticNetFilteringEngine, - cfe = µb.cosmeticFilteringEngine, - acceptedCount = snfe.acceptedCount + cfe.acceptedCount, - discardedCount = snfe.discardedCount + cfe.discardedCount; + sxfe = µb.staticExtFilteringEngine, + acceptedCount = snfe.acceptedCount + sxfe.acceptedCount, + discardedCount = snfe.discardedCount + sxfe.discardedCount; µb.applyCompiledFilters(compiled, assetKey === µb.userFiltersPath); if ( µb.availableFilterLists.hasOwnProperty(assetKey) ) { var entry = µb.availableFilterLists[assetKey]; - entry.entryCount = snfe.acceptedCount + cfe.acceptedCount - acceptedCount; - entry.entryUsedCount = entry.entryCount - (snfe.discardedCount + cfe.discardedCount - discardedCount); + entry.entryCount = snfe.acceptedCount + sxfe.acceptedCount - + acceptedCount; + entry.entryUsedCount = entry.entryCount - + (snfe.discardedCount + sxfe.discardedCount - discardedCount); } loadedListKeys.push(assetKey); }; @@ -650,7 +593,7 @@ µb.availableFilterLists = lists; µb.redirectEngine.reset(); - µb.cosmeticFilteringEngine.reset(); + µb.staticExtFilteringEngine.reset(); µb.staticNetFilteringEngine.reset(); µb.selfieManager.destroy(); µb.staticFilteringReverseLookup.resetLists(); @@ -767,23 +710,22 @@ /******************************************************************************/ µBlock.compileFilters = function(rawText) { - var networkFilters = new this.CompiledLineWriter(), - cosmeticFilters = new this.CompiledLineWriter(); + var writer = new this.CompiledLineWriter(); // Useful references: // https://adblockplus.org/en/filter-cheatsheet // https://adblockplus.org/en/filters var staticNetFilteringEngine = this.staticNetFilteringEngine, - cosmeticFilteringEngine = this.cosmeticFilteringEngine, + staticExtFilteringEngine = this.staticExtFilteringEngine, reIsWhitespaceChar = /\s/, reMaybeLocalIp = /^[\d:f]/, - reIsLocalhostRedirect = /\s+(?:broadcasthost|local|localhost|localhost\.localdomain)(?=\s|$)/, + reIsLocalhostRedirect = /\s+(?:broadcasthost|local|localhost|localhost\.localdomain)\b/, reLocalIp = /^(?:0\.0\.0\.0|127\.0\.0\.1|::1|fe80::1%lo0)/, - line, lineRaw, c, pos, + line, c, pos, lineIter = new this.LineIterator(rawText); while ( lineIter.eot() === false ) { - line = lineRaw = lineIter.next().trim(); + line = lineIter.next().trim(); // rhill 2014-04-18: The trim is important here, as without it there // could be a lingering `\r` which would cause problems in the @@ -797,9 +739,7 @@ // Parse or skip cosmetic filters // All cosmetic filters are caught here - if ( cosmeticFilteringEngine.compile(line, cosmeticFilters) ) { - continue; - } + if ( staticExtFilteringEngine.compile(line, writer) ) { continue; } // Whatever else is next can be assumed to not be a cosmetic filter @@ -831,12 +771,10 @@ if ( line.length === 0 ) { continue; } - staticNetFilteringEngine.compile(line, networkFilters); + staticNetFilteringEngine.compile(line, writer); } - return networkFilters.toString() + - '\n/* end of network - start of cosmetic */\n' + - cosmeticFilters.toString(); + return writer.toString(); }; /******************************************************************************/ @@ -847,15 +785,12 @@ µBlock.applyCompiledFilters = function(rawText, firstparty) { if ( rawText === '' ) { return; } - var separator = '\n/* end of network - start of cosmetic */\n', - pos = rawText.indexOf(separator), - reader = new this.CompiledLineReader(rawText.slice(0, pos)); + var reader = new this.CompiledLineReader(rawText); this.staticNetFilteringEngine.fromCompiledContent(reader); - this.cosmeticFilteringEngine.fromCompiledContent( - reader.reset(rawText.slice(pos + separator.length)), - this.userSettings.ignoreGenericCosmeticFilters, - !firstparty && !this.userSettings.parseAllABPHideFilters - ); + this.staticExtFilteringEngine.fromCompiledContent(reader, { + skipGenericCosmetic: this.userSettings.ignoreGenericCosmeticFilters, + skipCosmetic: !firstparty && !this.userSettings.parseAllABPHideFilters + }); }; /******************************************************************************/ @@ -949,7 +884,7 @@ availableFilterLists: this.availableFilterLists, staticNetFilteringEngine: this.staticNetFilteringEngine.toSelfie(), redirectEngine: this.redirectEngine.toSelfie(), - cosmeticFilteringEngine: this.cosmeticFilteringEngine.toSelfie() + staticExtFilteringEngine: this.staticExtFilteringEngine.toSelfie() }; vAPI.cacheStorage.set({ selfie: selfie }); }.bind(µBlock); @@ -1028,9 +963,6 @@ if ( Array.isArray(data.selectedFilterLists) ) { bin.selectedFilterLists = data.selectedFilterLists; binNotEmpty = true; - } else if ( typeof data.filterLists === 'object' ) { - bin.selectedFilterLists = µb.newListKeysFromOldData(data.filterLists); - binNotEmpty = true; } if ( typeof data.netWhitelist === 'string' ) { @@ -1135,7 +1067,7 @@ this.availableFilterLists.hasOwnProperty(details.assetKey) === false || this.selectedFilterLists.indexOf(details.assetKey) === -1 ) { - return false; + return; } } // https://github.com/gorhill/uBlock/issues/2594 @@ -1144,10 +1076,10 @@ this.hiddenSettings.ignoreRedirectFilters === true && this.hiddenSettings.ignoreScriptInjectFilters === true ) { - return false; + return; } } - return; + return true; } // Compile the list while we have the raw version in memory diff --git a/src/js/text-encode.js b/src/js/text-encode.js new file mode 100644 index 0000000000000..93ac0ff62b3c9 --- /dev/null +++ b/src/js/text-encode.js @@ -0,0 +1,265 @@ +/******************************************************************************* + + 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'; + +/******************************************************************************/ + +µBlock.textEncode = (function() { + + if ( µBlock.canFilterResponseBody !== true ) { return; } + + // charset aliases extracted from: + // https://github.com/inexorabletash/text-encoding/blob/b4e5bc26e26e51f56e3daa9f13138c79f49d3c34/lib/encoding.js#L342 + var normalizedCharset = new Map([ + [ 'utf8', 'utf-8' ], + [ 'unicode-1-1-utf-8', 'utf-8' ], + [ 'utf-8', 'utf-8' ], + + [ 'windows-1250', 'windows-1250' ], + [ 'cp1250', 'windows-1250' ], + [ 'x-cp1250', 'windows-1250' ], + + [ 'windows-1251', 'windows-1251' ], + [ 'cp1251', 'windows-1251' ], + [ 'x-cp1251', 'windows-1251' ], + + [ 'windows-1252', 'windows-1252' ], + [ 'ansi_x3.4-1968', 'windows-1252' ], + [ 'ascii', 'windows-1252' ], + [ 'cp1252', 'windows-1252' ], + [ 'cp819', 'windows-1252' ], + [ 'csisolatin1', 'windows-1252' ], + [ 'ibm819', 'windows-1252' ], + [ 'iso-8859-1', 'windows-1252' ], + [ 'iso-ir-100', 'windows-1252' ], + [ 'iso8859-1', 'windows-1252' ], + [ 'iso88591', 'windows-1252' ], + [ 'iso_8859-1', 'windows-1252' ], + [ 'iso_8859-1:1987', 'windows-1252' ], + [ 'l1', 'windows-1252' ], + [ 'latin1', 'windows-1252' ], + [ 'us-ascii', 'windows-1252' ], + [ 'x-cp1252', 'windows-1252' ], + ]); + + // http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1250.TXT + var cp1250_range0 = new Uint8Array([ + /* 0x0100 */ 0x00, 0x00, 0xC3, 0xE3, 0xA5, 0xB9, 0xC6, 0xE6, + /* 0x0108 */ 0x00, 0x00, 0x00, 0x00, 0xC8, 0xE8, 0xCF, 0xEF, + /* 0x0110 */ 0xD0, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x0118 */ 0xCA, 0xEA, 0xCC, 0xEC, 0x00, 0x00, 0x00, 0x00, + /* 0x0120 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x0128 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x0130 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x0138 */ 0x00, 0xC5, 0xE5, 0x00, 0x00, 0xBC, 0xBE, 0x00, + /* 0x0140 */ 0x00, 0xA3, 0xB3, 0xD1, 0xF1, 0x00, 0x00, 0xD2, + /* 0x0148 */ 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x0150 */ 0xD5, 0xF5, 0x00, 0x00, 0xC0, 0xE0, 0x00, 0x00, + /* 0x0158 */ 0xD8, 0xF8, 0x8C, 0x9C, 0x00, 0x00, 0xAA, 0xBA, + /* 0x0160 */ 0x8A, 0x9A, 0xDE, 0xFE, 0x8D, 0x9D, 0x00, 0x00, + /* 0x0168 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD9, 0xF9, + /* 0x0170 */ 0xDB, 0xFB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x0178 */ 0x00, 0x8F, 0x9F, 0xAF, 0xBF, 0x8E, 0x9E, 0x00 + ]); + + // http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1251.TXT + var cp1251_range0 = new Uint8Array([ + /* 0x0400 */ 0x00, 0xA8, 0x80, 0x81, 0xAA, 0xBD, 0xB2, 0xAF, + /* 0x0408 */ 0xA3, 0x8A, 0x8C, 0x8E, 0x8D, 0x00, 0xA1, 0x8F, + /* 0x0410 */ 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, + /* 0x0418 */ 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, + /* 0x0420 */ 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, + /* 0x0428 */ 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, + /* 0x0430 */ 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, + /* 0x0438 */ 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, + /* 0x0440 */ 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + /* 0x0448 */ 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, + /* 0x0450 */ 0x00, 0xB8, 0x90, 0x83, 0xBA, 0xBE, 0xB3, 0xBF, + /* 0x0458 */ 0xBC, 0x9A, 0x9C, 0x9E, 0x9D, 0x00, 0xA2, 0x9F, + /* 0x0460 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x0468 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x0470 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x0478 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x0480 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x0488 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x0490 */ 0xA5, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ]); + + // https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT + var cp1252_range0 = new Uint8Array([ + /* 0x0150 */ 0x00, 0x00, 0x8C, 0x9C, 0x00, 0x00, 0x00, 0x00, + /* 0x0158 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x0160 */ 0x8A, 0x9A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x0168 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x0170 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x0178 */ 0x9F, 0x00, 0x00, 0x00, 0x00, 0x8E, 0x9E, 0x00 + ]); + + var cp125x_range0 = new Uint8Array([ + /* 0x2010 */ 0x00, 0x00, 0x00, 0x96, 0x97, 0x00, 0x00, 0x00, + /* 0x2018 */ 0x91, 0x92, 0x82, 0x00, 0x93, 0x94, 0x84, 0x00, + /* 0x2020 */ 0x86, 0x87, 0x95, 0x00, 0x00, 0x00, 0x85, 0x00, + /* 0x2028 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x2030 */ 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 0x2038 */ 0x00, 0x8B, 0x9B, 0x00, 0x00, 0x00, 0x00, 0x00 + ]); + + var encoders = { + 'windows-1250': function(buf) { + var i = 0, n = buf.byteLength, o = 0, c; + while ( i < n ) { + c = buf[i++]; + if ( c < 0x80 ) { + buf[o++] = c; + } else { + if ( (c & 0xE0) === 0xC0 ) { + c = (c & 0x1F) << 6; + c |= (buf[i++] & 0x3F); + } else if ( (c & 0xF0) === 0xE0 ) { + c = (c & 0x0F) << 12; + c |= (buf[i++] & 0x3F) << 6; + c |= (buf[i++] & 0x3F); + } else if ( (c & 0xF8) === 0xF0 ) { + c = (c & 0x07) << 18; + c |= (buf[i++] & 0x3F) << 12; + c |= (buf[i++] & 0x3F) << 6; + c |= (buf[i++] & 0x3F); + } + if ( c < 0x100 ) { + buf[o++] = c; + } else if ( c < 0x180 ) { + buf[o++] = cp1250_range0[c - 0x100]; + } else if ( c >= 0x2010 && c < 0x2040 ) { + buf[o++] = cp125x_range0[c - 0x2010]; + } else if ( c === 0x02C7 ) { + buf[o++] = 0xA1; + } else if ( c === 0x02D8 ) { + buf[o++] = 0xA2; + } else if ( c === 0x02D9 ) { + buf[o++] = 0xFF; + } else if ( c === 0x02DB ) { + buf[o++] = 0xB2; + } else if ( c === 0x02DD ) { + buf[o++] = 0xBD; + } else if ( c === 0x20AC ) { + buf[o++] = 0x88; + } else if ( c === 0x2122 ) { + buf[o++] = 0x99; + } + } + } + return buf.slice(0, o); + }, + 'windows-1251': function(buf) { + var i = 0, n = buf.byteLength, o = 0, c; + while ( i < n ) { + c = buf[i++]; + if ( c < 0x80 ) { + buf[o++] = c; + } else { + if ( (c & 0xE0) === 0xC0 ) { + c = (c & 0x1F) << 6; + c |= (buf[i++] & 0x3F); + } else if ( (c & 0xF0) === 0xE0 ) { + c = (c & 0x0F) << 12; + c |= (buf[i++] & 0x3F) << 6; + c |= (buf[i++] & 0x3F); + } else if ( (c & 0xF8) === 0xF0 ) { + c = (c & 0x07) << 18; + c |= (buf[i++] & 0x3F) << 12; + c |= (buf[i++] & 0x3F) << 6; + c |= (buf[i++] & 0x3F); + } + if ( c < 0x100 ) { + buf[o++] = c; + } else if ( c >= 0x400 && c < 0x4A0 ) { + buf[o++] = cp1251_range0[c - 0x400]; + } else if ( c >= 0x2010 && c < 0x2040 ) { + buf[o++] = cp125x_range0[c - 0x2010]; + } else if ( c === 0x20AC ) { + buf[o++] = 0x88; + } else if ( c === 0x2116 ) { + buf[o++] = 0xB9; + } else if ( c === 0x2122 ) { + buf[o++] = 0x99; + } + } + } + return buf.slice(0, o); + }, + 'windows-1252': function(buf) { + var i = 0, n = buf.byteLength, o = 0, c; + while ( i < n ) { + c = buf[i++]; + if ( c < 0x80 ) { + buf[o++] = c; + } else { + if ( (c & 0xE0) === 0xC0 ) { + c = (c & 0x1F) << 6; + c |= (buf[i++] & 0x3F); + } else if ( (c & 0xF0) === 0xE0 ) { + c = (c & 0x0F) << 12; + c |= (buf[i++] & 0x3F) << 6; + c |= (buf[i++] & 0x3F); + } else if ( (c & 0xF8) === 0xF0 ) { + c = (c & 0x07) << 18; + c |= (buf[i++] & 0x3F) << 12; + c |= (buf[i++] & 0x3F) << 6; + c |= (buf[i++] & 0x3F); + } + if ( c < 0x100 ) { + buf[o++] = c; + } else if ( c >= 0x150 && c < 0x180 ) { + buf[o++] = cp1252_range0[c - 0x150]; + } else if ( c >= 0x2010 && c < 0x2040 ) { + buf[o++] = cp125x_range0[c - 0x2010]; + } else if ( c === 0x192 ) { + buf[o++] = 0x83; + } else if ( c === 0x2C6 ) { + buf[o++] = 0x88; + } else if ( c === 0x2DC ) { + buf[o++] = 0x98; + } else if ( c === 0x20AC ) { + buf[o++] = 0x80; + } else if ( c === 0x2122 ) { + buf[o++] = 0x99; + } + } + } + return buf.slice(0, o); + } + }; + + return { + encode: function(charset, buf) { + return encoders.hasOwnProperty(charset) ? + encoders[charset](buf) : + buf; + }, + normalizeCharset: function(charset) { + if ( charset === undefined ) { + return 'utf-8'; + } + return normalizedCharset.get(charset.toLowerCase()); + } + }; +})(); diff --git a/src/js/traffic.js b/src/js/traffic.js index eaea84158b4a6..456dfa8b16492 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2017 Raymond Hill + Copyright (C) 2014-2018 Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -480,9 +480,10 @@ onBeforeMaybeSpuriousCSPReport.textDecoder = undefined; /******************************************************************************/ // To handle: -// - inline script tags -// - websockets -// - media elements larger than n kB +// - Media elements larger than n kB +// - Scriptlet injection (requires ability to modify response body) +// - HTML filtering (requires ability to modify response body) +// - CSP injection var onHeadersReceived = function(details) { // Do not interfere with behind-the-scene requests. @@ -490,15 +491,17 @@ var onHeadersReceived = function(details) { if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } var µb = µBlock, - requestType = details.type; + requestType = details.type, + isRootDoc = requestType === 'main_frame', + isDoc = isRootDoc || requestType === 'sub_frame'; - if ( requestType === 'main_frame' ) { + if ( isRootDoc ) { µb.tabContextManager.push(tabId, details.url); } var pageStore = µb.pageStoreFromTabId(tabId); if ( pageStore === null ) { - if ( requestType !== 'main_frame' ) { return; } + if ( isRootDoc === false ) { return; } pageStore = µb.bindTabToPageStats(tabId, 'beforeRequest'); } if ( pageStore.getNetFilteringSwitch() === false ) { return; } @@ -507,24 +510,364 @@ var onHeadersReceived = function(details) { return foilLargeMediaElement(pageStore, details); } + if ( isDoc && µb.canFilterResponseBody ) { + filterDocument(pageStore, details); + } + // https://github.com/gorhill/uBlock/issues/2813 // Disable the blocking of large media elements if the document is itself // a media element: the resource was not prevented from loading so no // point to further block large media elements for the current document. - if ( requestType === 'main_frame' ) { + if ( isRootDoc ) { if ( reMediaContentTypes.test(headerValueFromName('content-type', details.responseHeaders)) ) { pageStore.allowLargeMediaElementsUntil = Date.now() + 86400000; } return injectCSP(pageStore, details); } - if ( requestType === 'sub_frame' ) { + if ( isDoc ) { return injectCSP(pageStore, details); } }; var reMediaContentTypes = /^(?:audio|image|video)\//; +/******************************************************************************* + + The response body filterer is responsible for: + + - Scriptlet filtering + - HTML filtering + + In the spirit of efficiency, the response body filterer works this way: + + If: + - HTML filtering: no. + - Scriptlet filtering: no. + Then: + No response body filtering is initiated. + + If: + - HTML filtering: no. + - Scriptlet filtering: yes. + Then: + Inject scriptlets before first chunk of response body data reported + then immediately disconnect response body data listener. + + If: + - HTML filtering: yes. + - Scriptlet filtering: no/yes. + Then: + Assemble all response body data into a single buffer. Once all the + response data has been received, create a document from it. Then: + - Inject scriptlets in the resulting DOM. + - Remove all DOM elements matching HTML filters. + Then serialize the resulting modified document as the new response + body. + + This way, the overhead is minimal for when only scriptlets need to be + injected. + + If the platform does not support response body filtering, the scriptlets + will be injected the old way, through the content script. + +**/ + +var filterDocument = (function() { + var µb = µBlock, + filterers = new Map(), + domParser, xmlSerializer, + utf8TextDecoder, textDecoder, textEncoder; + + var reContentTypeDocument = /^(?:text\/html|application\/xhtml+xml)/i, + reContentTypeCharset = /charset=['"]?([^'" ]+)/i; + + var charsetFromContentType = function(contentType) { + var match = reContentTypeCharset.exec(contentType); + if ( match !== null ) { + return match[1].toLowerCase(); + } + }; + + var charsetFromDoc = function(doc) { + var meta = doc.querySelector('meta[charset]'); + if ( meta !== null ) { + return meta.getAttribute('charset').toLowerCase(); + } + meta = doc.querySelector( + 'meta[http-equiv="content-type" i][content]' + ); + if ( meta !== null ) { + return charsetFromContentType(meta.getAttribute('content')); + } + }; + + // Purpose of following helper is to disconnect from watching the stream + // if all the following conditions are fulfilled: + // - Only need to inject scriptlets. + // - Charset of resource is utf-8. + // - A well-formed doc type declaration is found at the top. + // + // When all the conditions are fulfilled, then the scriptlets are safely + // injected after the doc type declaration, and the stream listener is + // disconnected, thus removing overhead from future streaming data. + // + // If at least one of the condition is not fulfilled and scriptlets need + // to be injected, it will be done the longer way when the whole stream + // has been collated in memory. + + var streamJobDone = function(filterer, responseBytes) { + if ( + filterer.scriptlets === undefined || + filterer.selectors !== undefined || + filterer.charset === undefined + ) { + return false; + } + // We need to insert after DOCTYPE, or else the browser may fall into + // quirks mode. + if ( responseBytes.byteLength < 256 ) { return false; } + var bb = new Uint8Array(responseBytes, 0, 256), + i = 0, b; + // Skip BOM if present. + if ( bb[0] === 0xEF && bb[1] === 0xBB && bb[2] === 0xBF ) { i += 3; } + // Scan for '<' + for (;;) { + b = bb[i++]; + if ( b === 0x3C /* '<' */ ) { break; } + if ( b > 0x20 || i > 240 ) { return false; } + } + // Case insensitively test for '!doctype'. + if ( + bb[i++] !== 0x21 /* '!' */ || + ( bb[i++] | 0x20 ) !== 0x64 /* 'd' */ || + ( bb[i++] | 0x20 ) !== 0x6F /* 'o' */ || + ( bb[i++] | 0x20 ) !== 0x63 /* 'c' */ || + ( bb[i++] | 0x20 ) !== 0x74 /* 't' */ || + ( bb[i++] | 0x20 ) !== 0x79 /* 'y' */ || + ( bb[i++] | 0x20 ) !== 0x70 /* 'p' */ || + ( bb[i++] | 0x20 ) !== 0x65 /* 'e' */ + ) { + return false; + } + // Scan for '>'. + var qcount = 0; + for (;;) { + b = bb[i++]; + if ( b === 0x3E /* '>' */ ) { break; } + if ( b === 0x22 /* '"' */ || b === 0x27 /* "'" */ ) { qcount += 1; } + if ( b > 0x7F || i > 240 ) { return false; } + } + // Bail out if mismatched quotes. + if ( (qcount & 1) !== 0 ) { return false; } + // We found a valid insertion point. + if ( textEncoder === undefined ) { textEncoder = new TextEncoder(); } + filterer.stream.write(new Uint8Array(responseBytes, 0, i)); + filterer.stream.write( + textEncoder.encode('') + ); + filterer.stream.write(new Uint8Array(responseBytes, i)); + filterer.stream.disconnect(); + return true; + }; + + var streamClose = function(filterer, buffer) { + if ( buffer !== undefined ) { + filterer.stream.write(buffer); + } else if ( filterer.buffer !== undefined ) { + filterer.stream.write(filterer.buffer); + } + filterer.stream.close(); + }; + + var onStreamData = function(ev) { + var filterer = filterers.get(this); + if ( filterer === undefined ) { + this.write(ev.data); + this.disconnect(); + return; + } + if ( + this.status !== 'transferringdata' && + this.status !== 'finishedtransferringdata' + ) { + filterers.delete(this); + this.disconnect(); + return; + } + // TODO: + // - Possibly improve buffer growth, if benchmarking shows it's worth + // it. + // - Also evaluate whether keeping a list of buffers and then decoding + // them in sequence using TextDecoder's "stream" option is more + // efficient. Can the data buffers be safely kept around for later + // use? + // - Informal, quick benchmarks seem to show most of the overhead is + // from calling TextDecoder.decode() and TextEncoder.encode(), and if + // confirmed, there is nothing which can be done uBO-side to reduce + // overhead. + if ( filterer.buffer === null ) { + if ( streamJobDone(filterer, ev.data) ) { return; } + filterer.buffer = new Uint8Array(ev.data); + return; + } + var buffer = new Uint8Array( + filterer.buffer.byteLength + + ev.data.byteLength + ); + buffer.set(filterer.buffer); + buffer.set(new Uint8Array(ev.data), filterer.buffer.byteLength); + filterer.buffer = buffer; + }; + + var onStreamStop = function() { + var filterer = filterers.get(this); + filterers.delete(this); + if ( filterer === undefined || filterer.buffer === null ) { + this.close(); + return; + } + if ( this.status !== 'finishedtransferringdata' ) { return; } + + if ( domParser === undefined ) { + domParser = new DOMParser(); + xmlSerializer = new XMLSerializer(); + } + if ( textEncoder === undefined ) { + textEncoder = new TextEncoder(); + } + + var doc; + + // If stream encoding is still unknnown, try to extract from document. + if ( filterer.charset === undefined ) { + if ( utf8TextDecoder === undefined ) { + utf8TextDecoder = new TextDecoder(); + } + doc = domParser.parseFromString( + utf8TextDecoder.decode(filterer.buffer.slice(0, 1024)), + 'text/html' + ); + filterer.charset = µb.textEncode.normalizeCharset(charsetFromDoc(doc)); + if ( filterer.charset === undefined ) { + streamClose(filterer); + return; + } + } + + if ( + textDecoder !== undefined && + textDecoder.encoding !== filterer.charset + ) { + textDecoder = undefined; + } + if ( textDecoder === undefined ) { + textDecoder = new TextDecoder(filterer.charset); + } + + doc = domParser.parseFromString( + textDecoder.decode(filterer.buffer), + 'text/html' + ); + + var modified = false; + if ( filterer.selectors !== undefined ) { + if ( µb.htmlFilteringEngine.apply(doc, filterer) ) { + modified = true; + } + } + if ( filterer.scriptlets !== undefined ) { + if ( µb.scriptletFilteringEngine.apply(doc, filterer) ) { + modified = true; + } + } + + if ( modified === false ) { + streamClose(filterer); + return; + } + + // https://stackoverflow.com/questions/6088972/get-doctype-of-an-html-as-string-with-javascript/10162353#10162353 + var doctypeStr = doc.doctype instanceof Object ? + xmlSerializer.serializeToString(doc.doctype) + '\n' : + ''; + + // https://github.com/gorhill/uBlock/issues/3391 + var encodedStream = textEncoder.encode( + doctypeStr + + doc.documentElement.outerHTML + ); + if ( filterer.charset !== 'utf-8' ) { + encodedStream = µb.textEncode.encode( + filterer.charset, + encodedStream + ); + } + + streamClose(filterer, encodedStream); + }; + + var onStreamError = function() { + filterers.delete(this); + }; + + return function(pageStore, details) { + // https://github.com/gorhill/uBlock/issues/3478 + var statusCode = details.statusCode || 0; + if ( statusCode !== 0 && (statusCode < 200 || statusCode >= 300) ) { + return; + } + + var hostname = µb.URI.hostnameFromURI(details.url); + if ( hostname === '' ) { return; } + + var domain = µb.URI.domainFromHostname(hostname); + + var request = { + stream: undefined, + tabId: details.tabId, + url: details.url, + hostname: hostname, + domain: domain, + entity: µb.URI.entityFromDomain(domain), + selectors: undefined, + scriptlets: undefined, + buffer: null, + charset: undefined + }; + request.selectors = µb.htmlFilteringEngine.retrieve(request); + request.scriptlets = µb.scriptletFilteringEngine.retrieve(request); + + if ( + request.selectors === undefined && + request.scriptlets === undefined + ) { + return; + } + + var headers = details.responseHeaders, + contentType = headerValueFromName('content-type', headers); + if ( contentType !== '' ) { + if ( reContentTypeDocument.test(contentType) === false ) { return; } + var charset = charsetFromContentType(contentType); + if ( charset !== undefined ) { + charset = µb.textEncode.normalizeCharset(charset); + if ( charset === undefined ) { return; } + request.charset = charset; + } + } + // https://bugzilla.mozilla.org/show_bug.cgi?id=1426789 + if ( headerValueFromName('content-disposition', headers) ) { return; } + + var stream = request.stream = + vAPI.net.webRequest.filterResponseData(details.requestId); + stream.ondata = onStreamData; + stream.onstop = onStreamStop; + stream.onerror = onStreamError; + filterers.set(stream, request); + }; +})(); + /******************************************************************************/ var injectCSP = function(pageStore, details) { diff --git a/src/js/uritools.js b/src/js/uritools.js index f624d891edf13..a988af4e0053d 100644 --- a/src/js/uritools.js +++ b/src/js/uritools.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2016 Raymond Hill + Copyright (C) 2014-2017 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 @@ -51,7 +51,7 @@ var reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/; // Derived var reSchemeFromURI = /^[^:\/?#]+:/; var reAuthorityFromURI = /^(?:[^:\/?#]+:)?(\/\/[^\/?#]+)/; -var reOriginFromURI = /^(?:[^:\/?#]+:)?(?:\/\/[^\/?#]+)/; +var reOriginFromURI = /^(?:[^:\/?#]+:)\/\/(?:[^\/?#]+)?/; var reCommonHostnameFromURL = /^https?:\/\/([0-9a-z_][0-9a-z._-]*[0-9a-z])\//; var rePathFromURI = /^(?:[^:\/?#]+:)?(?:\/\/[^\/?#]*)?([^?#]*)/; var reMustNormalizeHostname = /[^0-9a-z._-]/; @@ -309,6 +309,13 @@ var psl = publicSuffixList; /******************************************************************************/ +URI.entityFromDomain = function(domain) { + var pos = domain.indexOf('.'); + return pos !== -1 ? domain.slice(0, pos) + '.*' : ''; +}; + +/******************************************************************************/ + URI.pathFromURI = function(uri) { var matches = rePathFromURI.exec(uri); return matches !== null ? matches[1] : ''; diff --git a/src/js/utils.js b/src/js/utils.js index fdde0d3ecdeba..918a49b1b4f9e 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -225,7 +225,9 @@ /******************************************************************************/ µBlock.CompiledLineWriter = function() { - this.output = []; + this.blockId = undefined; + this.block = undefined; + this.blocks = new Map(); this.stringifier = JSON.stringify; }; @@ -235,46 +237,81 @@ µBlock.CompiledLineWriter.prototype = { push: function(args) { - this.output[this.output.length] = this.stringifier(args); + this.block[this.block.length] = this.stringifier(args); + }, + select: function(blockId) { + if ( blockId === this.blockId ) { return; } + this.blockId = blockId; + this.block = this.blocks.get(blockId); + if ( this.block === undefined ) { + this.blocks.set(blockId, (this.block = [])); + } }, toString: function() { - return this.output.join('\n'); + var result = []; + for ( var entry of this.blocks ) { + if ( entry[1].length === 0 ) { continue; } + result.push( + '#block-start-' + entry[0], + entry[1].join('\n'), + '#block-end-' + entry[0] + ); + } + return result.join('\n'); } }; -µBlock.CompiledLineReader = function(raw) { - this.reset(raw); +/******************************************************************************/ + +µBlock.CompiledLineReader = function(raw, blockId) { + this.block = ''; + this.len = 0; + this.offset = 0; + this.line = ''; this.parser = JSON.parse; + this.blocks = new Map(); + var reBlockStart = /^#block-start-(\d+)\n/gm, + match = reBlockStart.exec(raw), + beg, end; + while ( match !== null ) { + beg = match.index + match[0].length; + end = raw.indexOf('#block-end-' + match[1], beg); + this.blocks.set(parseInt(match[1], 10), raw.slice(beg, end)); + reBlockStart.lastIndex = end; + match = reBlockStart.exec(raw); + } + if ( blockId !== undefined ) { + this.select(blockId); + } }; µBlock.CompiledLineReader.prototype = { - reset: function(raw) { - this.input = raw; - this.len = raw.length; - this.offset = 0; - this.s = ''; - return this; - }, next: function() { if ( this.offset === this.len ) { - this.s = ''; + this.line = ''; return false; } - var pos = this.input.indexOf('\n', this.offset); + var pos = this.block.indexOf('\n', this.offset); if ( pos !== -1 ) { - this.s = this.input.slice(this.offset, pos); + this.line = this.block.slice(this.offset, pos); this.offset = pos + 1; } else { - this.s = this.input.slice(this.offset); + this.line = this.block.slice(this.offset); this.offset = this.len; } return true; }, + select: function(blockId) { + this.block = this.blocks.get(blockId) || ''; + this.len = this.block.length; + this.offset = 0; + return this; + }, fingerprint: function() { - return this.s; + return this.line; }, args: function() { - return this.parser(this.s); + return this.parser(this.line); } }; @@ -351,6 +388,7 @@ this.size = size; this.array = []; this.map = new Map(); + this.resetTime = Date.now(); }; µBlock.MRUCache.prototype = { @@ -372,14 +410,18 @@ lookup: function(key) { var value = this.map.get(key); if ( value !== undefined && this.array[0] !== key ) { - this.array.splice(this.array.indexOf(key), 1); - this.array.unshift(key); + var i = this.array.indexOf(key); + do { + this.array[i] = this.array[i-1]; + } while ( --i ); + this.array[0] = key; } return value; }, reset: function() { this.array = []; this.map.clear(); + this.resetTime = Date.now(); } }; diff --git a/src/lib/yamd5.js b/src/lib/yamd5.js deleted file mode 100644 index 582b5764a4378..0000000000000 --- a/src/lib/yamd5.js +++ /dev/null @@ -1,402 +0,0 @@ -/******************************************************************************* - -YaMD5 - Yet another MD5 hasher. -home: https://github.com/gorhill/yamd5.js - -I needed an MD5 hasher, and as usual I want small code base, and fast. - -Originally found md5-o-matic [1]. It was fast but did not work with Unicode -strings. Also, eventually realized it was really based on code from -Joseph Myers [2] with no proper credits given (not nice). - -Then I found SparkMD5 [3], which works with Unicode strings, but at a steep -cost to performance. Also, glancing at the code I saw avoidable redundancies -causing the code base to be much larger than needed. - -So from this point I set out to write my own version, YaMD5 (sorry, I am -not good with naming projects), of course heavily relying on the original -code from Joseph Myers [2], and bits from SparkMD5 -- I started to work from -SparkMD5 implementation, so there might be bits of code original to SparkMD5 -code left in a few places (like say, MD5.end()). - -Advantages of YaMD5: - -- Can handle Unicode strings -- Natively incremental -- Small code base -- Fastest MD5 hasher out there so far for large input [4] -- Even faster than versions supporting only simpler ascii strings - - - [1] https://github.com/trentmillar/md5-o-matic - [2] http://www.myersdaily.org/joseph/javascript/md5-text.html - [3] https://github.com/satazor/SparkMD5 - [4] http://jsperf.com/md5-shootout/75 - -So with that said, I don't know what license covers Joseph Myers' code (need -to find out). In any case, concerning whatever original code I contributed in -there: - -The MIT License (MIT) - -Copyright (C) 2014 Raymond Hill - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -**/ - -/* jshint bitwise: false */ - -(function(root) { - - 'use strict'; - - /* - * Fastest md5 implementation around (JKM md5) - * Credits: Joseph Myers - * - * @see http://www.myersdaily.org/joseph/javascript/md5-text.html - * @see http://jsperf.com/md5-shootout/7 - */ - - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence - - var md5cycle = function(x, k) { - var a = x[0], - b = x[1], - c = x[2], - d = x[3]; - // ff() - a += (b & c | ~b & d) + k[0] - 680876936 | 0; - a = (a << 7 | a >>> 25) + b | 0; - d += (a & b | ~a & c) + k[1] - 389564586 | 0; - d = (d << 12 | d >>> 20) + a | 0; - c += (d & a | ~d & b) + k[2] + 606105819 | 0; - c = (c << 17 | c >>> 15) + d | 0; - b += (c & d | ~c & a) + k[3] - 1044525330 | 0; - b = (b << 22 | b >>> 10) + c | 0; - a += (b & c | ~b & d) + k[4] - 176418897 | 0; - a = (a << 7 | a >>> 25) + b | 0; - d += (a & b | ~a & c) + k[5] + 1200080426 | 0; - d = (d << 12 | d >>> 20) + a | 0; - c += (d & a | ~d & b) + k[6] - 1473231341 | 0; - c = (c << 17 | c >>> 15) + d | 0; - b += (c & d | ~c & a) + k[7] - 45705983 | 0; - b = (b << 22 | b >>> 10) + c | 0; - a += (b & c | ~b & d) + k[8] + 1770035416 | 0; - a = (a << 7 | a >>> 25) + b | 0; - d += (a & b | ~a & c) + k[9] - 1958414417 | 0; - d = (d << 12 | d >>> 20) + a | 0; - c += (d & a | ~d & b) + k[10] - 42063 | 0; - c = (c << 17 | c >>> 15) + d | 0; - b += (c & d | ~c & a) + k[11] - 1990404162 | 0; - b = (b << 22 | b >>> 10) + c | 0; - a += (b & c | ~b & d) + k[12] + 1804603682 | 0; - a = (a << 7 | a >>> 25) + b | 0; - d += (a & b | ~a & c) + k[13] - 40341101 | 0; - d = (d << 12 | d >>> 20) + a | 0; - c += (d & a | ~d & b) + k[14] - 1502002290 | 0; - c = (c << 17 | c >>> 15) + d | 0; - b += (c & d | ~c & a) + k[15] + 1236535329 | 0; - b = (b << 22 | b >>> 10) + c | 0; - // gg() - a += (b & d | c & ~d) + k[1] - 165796510 | 0; - a = (a << 5 | a >>> 27) + b | 0; - d += (a & c | b & ~c) + k[6] - 1069501632 | 0; - d = (d << 9 | d >>> 23) + a | 0; - c += (d & b | a & ~b) + k[11] + 643717713 | 0; - c = (c << 14 | c >>> 18) + d | 0; - b += (c & a | d & ~a) + k[0] - 373897302 | 0; - b = (b << 20 | b >>> 12) + c | 0; - a += (b & d | c & ~d) + k[5] - 701558691 | 0; - a = (a << 5 | a >>> 27) + b | 0; - d += (a & c | b & ~c) + k[10] + 38016083 | 0; - d = (d << 9 | d >>> 23) + a | 0; - c += (d & b | a & ~b) + k[15] - 660478335 | 0; - c = (c << 14 | c >>> 18) + d | 0; - b += (c & a | d & ~a) + k[4] - 405537848 | 0; - b = (b << 20 | b >>> 12) + c | 0; - a += (b & d | c & ~d) + k[9] + 568446438 | 0; - a = (a << 5 | a >>> 27) + b | 0; - d += (a & c | b & ~c) + k[14] - 1019803690 | 0; - d = (d << 9 | d >>> 23) + a | 0; - c += (d & b | a & ~b) + k[3] - 187363961 | 0; - c = (c << 14 | c >>> 18) + d | 0; - b += (c & a | d & ~a) + k[8] + 1163531501 | 0; - b = (b << 20 | b >>> 12) + c | 0; - a += (b & d | c & ~d) + k[13] - 1444681467 | 0; - a = (a << 5 | a >>> 27) + b | 0; - d += (a & c | b & ~c) + k[2] - 51403784 | 0; - d = (d << 9 | d >>> 23) + a | 0; - c += (d & b | a & ~b) + k[7] + 1735328473 | 0; - c = (c << 14 | c >>> 18) + d | 0; - b += (c & a | d & ~a) + k[12] - 1926607734 | 0; - b = (b << 20 | b >>> 12) + c | 0; - // hh() - a += (b ^ c ^ d) + k[5] - 378558 | 0; - a = (a << 4 | a >>> 28) + b | 0; - d += (a ^ b ^ c) + k[8] - 2022574463 | 0; - d = (d << 11 | d >>> 21) + a | 0; - c += (d ^ a ^ b) + k[11] + 1839030562 | 0; - c = (c << 16 | c >>> 16) + d | 0; - b += (c ^ d ^ a) + k[14] - 35309556 | 0; - b = (b << 23 | b >>> 9) + c | 0; - a += (b ^ c ^ d) + k[1] - 1530992060 | 0; - a = (a << 4 | a >>> 28) + b | 0; - d += (a ^ b ^ c) + k[4] + 1272893353 | 0; - d = (d << 11 | d >>> 21) + a | 0; - c += (d ^ a ^ b) + k[7] - 155497632 | 0; - c = (c << 16 | c >>> 16) + d | 0; - b += (c ^ d ^ a) + k[10] - 1094730640 | 0; - b = (b << 23 | b >>> 9) + c | 0; - a += (b ^ c ^ d) + k[13] + 681279174 | 0; - a = (a << 4 | a >>> 28) + b | 0; - d += (a ^ b ^ c) + k[0] - 358537222 | 0; - d = (d << 11 | d >>> 21) + a | 0; - c += (d ^ a ^ b) + k[3] - 722521979 | 0; - c = (c << 16 | c >>> 16) + d | 0; - b += (c ^ d ^ a) + k[6] + 76029189 | 0; - b = (b << 23 | b >>> 9) + c | 0; - a += (b ^ c ^ d) + k[9] - 640364487 | 0; - a = (a << 4 | a >>> 28) + b | 0; - d += (a ^ b ^ c) + k[12] - 421815835 | 0; - d = (d << 11 | d >>> 21) + a | 0; - c += (d ^ a ^ b) + k[15] + 530742520 | 0; - c = (c << 16 | c >>> 16) + d | 0; - b += (c ^ d ^ a) + k[2] - 995338651 | 0; - b = (b << 23 | b >>> 9) + c | 0; - // ii() - a += (c ^ (b | ~d)) + k[0] - 198630844 | 0; - a = (a << 6 | a >>> 26) + b | 0; - d += (b ^ (a | ~c)) + k[7] + 1126891415 | 0; - d = (d << 10 | d >>> 22) + a | 0; - c += (a ^ (d | ~b)) + k[14] - 1416354905 | 0; - c = (c << 15 | c >>> 17) + d | 0; - b += (d ^ (c | ~a)) + k[5] - 57434055 | 0; - b = (b << 21 |b >>> 11) + c | 0; - a += (c ^ (b | ~d)) + k[12] + 1700485571 | 0; - a = (a << 6 | a >>> 26) + b | 0; - d += (b ^ (a | ~c)) + k[3] - 1894986606 | 0; - d = (d << 10 | d >>> 22) + a | 0; - c += (a ^ (d | ~b)) + k[10] - 1051523 | 0; - c = (c << 15 | c >>> 17) + d | 0; - b += (d ^ (c | ~a)) + k[1] - 2054922799 | 0; - b = (b << 21 |b >>> 11) + c | 0; - a += (c ^ (b | ~d)) + k[8] + 1873313359 | 0; - a = (a << 6 | a >>> 26) + b | 0; - d += (b ^ (a | ~c)) + k[15] - 30611744 | 0; - d = (d << 10 | d >>> 22) + a | 0; - c += (a ^ (d | ~b)) + k[6] - 1560198380 | 0; - c = (c << 15 | c >>> 17) + d | 0; - b += (d ^ (c | ~a)) + k[13] + 1309151649 | 0; - b = (b << 21 |b >>> 11) + c | 0; - a += (c ^ (b | ~d)) + k[4] - 145523070 | 0; - a = (a << 6 | a >>> 26) + b | 0; - d += (b ^ (a | ~c)) + k[11] - 1120210379 | 0; - d = (d << 10 | d >>> 22) + a | 0; - c += (a ^ (d | ~b)) + k[2] + 718787259 | 0; - c = (c << 15 | c >>> 17) + d | 0; - b += (d ^ (c | ~a)) + k[9] - 343485551 | 0; - b = (b << 21 | b >>> 11) + c | 0; - - x[0] = a + x[0] | 0; - x[1] = b + x[1] | 0; - x[2] = c + x[2] | 0; - x[3] = d + x[3] | 0; - }; - - var hexChars = '0123456789abcdef'; - var hexOut = []; - - var hex = function(x) { - var hc = hexChars; - var ho = hexOut; - var n, offset, j; - for (var i = 0; i < 4; i++) { - offset = i * 8; - n = x[i]; - for ( j = 0; j < 8; j += 2 ) { - ho[offset+1+j] = hc.charAt(n & 0x0F); - n >>>= 4; - ho[offset+0+j] = hc.charAt(n & 0x0F); - n >>>= 4; - } - } - return ho.join(''); - }; - - var MD5 = function() { - this._dataLength = 0; - this._state = new Int32Array(4); - this._buffer = new ArrayBuffer(68); - this._bufferLength = 0; - this._buffer8 = new Uint8Array(this._buffer, 0, 68); - this._buffer32 = new Uint32Array(this._buffer, 0, 17); - this.start(); - }; - - var stateIdentity = new Int32Array([1732584193, -271733879, -1732584194, 271733878]); - var buffer32Identity = new Int32Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); - - // Char to code point to to array conversion: - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt#Example.3A_Fixing_charCodeAt_to_handle_non-Basic-Multilingual-Plane_characters_if_their_presence_earlier_in_the_string_is_unknown - MD5.prototype.appendStr = function(str) { - var buf8 = this._buffer8; - var buf32 = this._buffer32; - var bufLen = this._bufferLength; - var code; - for ( var i = 0; i < str.length; i++ ) { - code = str.charCodeAt(i); - if ( code < 128 ) { - buf8[bufLen++] = code; - } else if ( code < 0x800 ) { - buf8[bufLen++] = (code >>> 6) + 0xC0; - buf8[bufLen++] = code & 0x3F | 0x80; - } else if ( code < 0xD800 || code > 0xDBFF ) { - buf8[bufLen++] = (code >>> 12) + 0xE0; - buf8[bufLen++] = (code >>> 6 & 0x3F) | 0x80; - buf8[bufLen++] = (code & 0x3F) | 0x80; - } else { - code = ((code - 0xD800) * 0x400) + (str.charCodeAt(++i) - 0xDC00) + 0x10000; - if ( code > 0x10FFFF ) { - throw 'Unicode standard supports code points up to U+10FFFF'; - } - buf8[bufLen++] = (code >>> 18) + 0xF0; - buf8[bufLen++] = (code >>> 12 & 0x3F) | 0x80; - buf8[bufLen++] = (code >>> 6 & 0x3F) | 0x80; - buf8[bufLen++] = (code & 0x3F) | 0x80; - } - if ( bufLen >= 64 ) { - this._dataLength += 64; - md5cycle(this._state, buf32); - bufLen -= 64; - buf32[0] = buf32[16]; - } - } - this._bufferLength = bufLen; - return this; - }; - - MD5.prototype.appendAsciiStr = function(str) { - var buf8 = this._buffer8; - var buf32 = this._buffer32; - var bufLen = this._bufferLength; - var i, j = 0; - for (;;) { - i = Math.min(str.length-j, 64-bufLen); - while ( i-- ) { - buf8[bufLen++] = str.charCodeAt(j++); - } - if ( bufLen < 64 ) { - break; - } - this._dataLength += 64; - md5cycle(this._state, buf32); - bufLen = 0; - } - this._bufferLength = bufLen; - return this; - }; - - MD5.prototype.appendByteArray = function(input) { - var buf8 = this._buffer8; - var buf32 = this._buffer32; - var bufLen = this._bufferLength; - var i, j = 0; - for (;;) { - i = Math.min(input.length-j, 64-bufLen); - while ( i-- ) { - buf8[bufLen++] = input[j++]; - } - if ( bufLen < 64 ) { - break; - } - this._dataLength += 64; - md5cycle(this._state, buf32); - bufLen = 0; - } - this._bufferLength = bufLen; - return this; - }; - - MD5.prototype.start = function() { - this._dataLength = 0; - this._bufferLength = 0; - this._state.set(stateIdentity); - return this; - }; - - MD5.prototype.end = function(raw) { - var bufLen = this._bufferLength; - this._dataLength += bufLen; - var buf8 = this._buffer8; - buf8[bufLen] = 0x80; - buf8[bufLen+1] = buf8[bufLen+2] = buf8[bufLen+3] = 0; - var buf32 = this._buffer32; - var i = (bufLen >> 2) + 1; - buf32.set(buffer32Identity.subarray(i), i); - if (bufLen > 55) { - md5cycle(this._state, buf32); - buf32.set(buffer32Identity); - } - // Do the final computation based on the tail and length - // Beware that the final length may not fit in 32 bits so we take care of that - var dataBitsLen = this._dataLength * 8; - if ( dataBitsLen <= 0xFFFFFFFF ) { - buf32[14] = dataBitsLen; - } else { - var matches = dataBitsLen.toString(16).match(/(.*?)(.{0,8})$/); - var lo = parseInt(matches[2], 16); - var hi = parseInt(matches[1], 16) || 0; - buf32[14] = lo; - buf32[15] = hi; - } - md5cycle(this._state, buf32); - - return !!raw ? this._state : hex(this._state); - }; - - // This permanent instance is to use for one-call hashing - var onePassHasher = new MD5(); - - MD5.hashStr = function(str, raw) { - return onePassHasher - .start() - .appendStr(str) - .end(raw); - }; - - MD5.hashAsciiStr = function(str, raw) { - return onePassHasher - .start() - .appendAsciiStr(str) - .end(raw); - }; - - // Self-test - // In some cases the fast add32 function cannot be used.. - if ( MD5.hashStr('hello') !== '5d41402abc4b2a76b9719d911017c592' ) { - console.error('YaMD5> this javascript engine does not support YaMD5. Sorry.'); - } - - if ( typeof root === 'object' ) { - root.YaMD5 = MD5; - } - return MD5; -})(this); diff --git a/src/logger-ui.html b/src/logger-ui.html index 9539f44a90e79..804a1363faeaa 100644 --- a/src/logger-ui.html +++ b/src/logger-ui.html @@ -15,6 +15,7 @@ diff --git a/tools/import-crowdin.sh b/tools/import-crowdin.sh index f12a800b5ee48..06c8af6e2366f 100755 --- a/tools/import-crowdin.sh +++ b/tools/import-crowdin.sh @@ -36,6 +36,7 @@ cp $SRC/id/messages.json $DES/id/messages.json cp $SRC/it/messages.json $DES/it/messages.json cp $SRC/ja/messages.json $DES/ja/messages.json cp $SRC/ka/messages.json $DES/ka/messages.json +cp $SRC/kk/messages.json $DES/kk/messages.json cp $SRC/kn/messages.json $DES/kn/messages.json cp $SRC/ko/messages.json $DES/ko/messages.json cp $SRC/lt/messages.json $DES/lt/messages.json @@ -43,7 +44,7 @@ cp $SRC/lv/messages.json $DES/lv/messages.json cp $SRC/ml-IN/messages.json $DES/ml/messages.json cp $SRC/mr/messages.json $DES/mr/messages.json cp $SRC/ms/messages.json $DES/ms/messages.json -cp $SRC/no/messages.json $DES/nb/messages.json +cp $SRC/nb/messages.json $DES/nb/messages.json cp $SRC/nl/messages.json $DES/nl/messages.json cp $SRC/pl/messages.json $DES/pl/messages.json cp $SRC/pt-BR/messages.json $DES/pt_BR/messages.json @@ -93,6 +94,7 @@ cp $SRC/id/description.txt $DES/description-id.txt cp $SRC/it/description.txt $DES/description-it.txt cp $SRC/ja/description.txt $DES/description-ja.txt cp $SRC/ka/description.txt $DES/description-ka.txt +cp $SRC/kk/description.txt $DES/description-kk.txt cp $SRC/ko/description.txt $DES/description-ko.txt cp $SRC/kn/description.txt $DES/description-kn.txt cp $SRC/lt/description.txt $DES/description-lt.txt @@ -100,7 +102,7 @@ cp $SRC/lv/description.txt $DES/description-lv.txt cp $SRC/ml-IN/description.txt $DES/description-ml.txt cp $SRC/ms/description.txt $DES/description-ms.txt cp $SRC/mr/description.txt $DES/description-mr.txt -cp $SRC/no/description.txt $DES/description-no.txt +cp $SRC/nb/description.txt $DES/description-nb.txt cp $SRC/nl/description.txt $DES/description-nl.txt cp $SRC/pl/description.txt $DES/description-pl.txt cp $SRC/pt-BR/description.txt $DES/description-pt_BR.txt diff --git a/tools/make-assets.sh b/tools/make-assets.sh index ec3c5b38daca2..4e9e26a28f095 100755 --- a/tools/make-assets.sh +++ b/tools/make-assets.sh @@ -8,7 +8,7 @@ printf "*** Packaging assets in $DES... " if [ -n "${TRAVIS_TAG}" ]; then pushd .. > /dev/null - git clone https://github.com/uBlockOrigin/uAssets.git + git clone --depth 1 https://github.com/uBlockOrigin/uAssets.git popd > /dev/null fi @@ -25,5 +25,7 @@ cp -R ../uAssets/thirdparties/www.malwaredomainlist.com $DES/thirdparti mkdir $DES/ublock cp -R ../uAssets/filters/* $DES/ublock/ +# Optional filter lists: do not include in package +rm $DES/ublock/annoyances.txt echo "done." diff --git a/tools/make-opera.sh b/tools/make-opera.sh index 596b7ce82edb6..7e00d3287511e 100755 --- a/tools/make-opera.sh +++ b/tools/make-opera.sh @@ -23,11 +23,19 @@ cp platform/chromium/*.html $DES/ cp platform/chromium/*.json $DES/ cp LICENSE.txt $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/contentscript.js >> /tmp/contentscript.js +mv /tmp/contentscript.js $DES/js/contentscript.js +rm $DES/js/vapi-usercss.js + # Opera-specific cp platform/opera/manifest.json $DES/ rm -r $DES/_locales/cv rm -r $DES/_locales/hi rm -r $DES/_locales/ka +rm -r $DES/_locales/kk rm -r $DES/_locales/mr rm -r $DES/_locales/ta