diff --git a/.jshintrc b/.jshintrc index de72266b3..fd2d65d0c 100644 --- a/.jshintrc +++ b/.jshintrc @@ -4,7 +4,8 @@ "eqeqeq": true, "esnext": true, "globals": { - "chrome": false, + "browser": false, // global variable in Firefox, Edge + "chrome": false, // global variable in Chromium, Chrome, Opera "Components": false, // global variable in Firefox "safari": false, "self": false, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b3ef10f65..9cc3e0840 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,14 @@ # Submitting issues -For **support/discussions**, there is [Mozilla Discourse](https://discourse.mozilla-community.org/t/support-ublock-origin/6746). +Please stop opening invalid issues regarding the "Legacy" label of uBlock Origin on AMO, it's all explained in the [Release notes](https://github.com/gorhill/uBlock/releases). + +For **support/discussions/help**, there is [/r/uBlockOrigin](https://www.reddit.com/r/uBlockOrigin/) on Reddit -- this is where I see the most activity for people helping each other regarding uBlock Origin. For **filter-related issues**, report on the respective filter list support site, or at [uBlockOrigin/uAssets](https://github.com/uBlockOrigin/uAssets/issues). Use [the logger](https://github.com/gorhill/uBlock/wiki/The-logger) to diagnose/confirm filter-related issues. If something does not work properly with uBO enabled, the **first step** is to rule out filter-related issues. Ignorance of the above rules is no excuse: **Opening an issue for purpose of support or discussion, or opening a filter-related issue will result in the user being immediately blocked.** Given the [amount of invalid issues being opened](https://github.com/gorhill/uBlock/issues?q=is%3Aissue+label%3Ainvalid+is%3Aclosed), I have no choice but to resort to such a drastic measure. You will still be able to open filter list issues at [uBlockOrigin/uAssets](https://github.com/uBlockOrigin/uAssets/issues). -**The issue tracker is for provable issues only:** You will have to make the case that the issue is really with uBlock Origin and not something else on your side. To make a case means to provide detailed steps so that anybody can reproduce the issue. Be sure to rule out that the issue is not caused by something specific on your side. +**The issue tracker is for provable issues only:** You will have to make the case that the issue is really with uBlock Origin and not something else on your side. To make a case means to provide detailed steps so that anybody can reproduce the issue. Be sure to rule out that the issue is not caused by something specific on your side. Specifically, _speculated_ performance issues will be marked as invalid and closed if they do not come with **actual profiling data + analysis** supporting the claim. **Any issue opened without effort to provide the required details for me (or anybody else) to reproduce the problem will be closed as _invalid_.** If you provide more details thereafter for me to reproduce the issue, I will reopen it if I can confirm there is indeed an issue with uBlock Origin. Example of detailed steps: diff --git a/README.md b/README.md index 19ec43698..e55315ccc 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ [![Build](https://travis-ci.org/gorhill/uBlock.svg?branch=master)](https://travis-ci.org/gorhill/uBlock) -[![Crowdin](https://d322cqt584bo4o.cloudfront.net/ublock/localized.png)](https://crowdin.com/project/ublock) +[![Crowdin](https://d322cqt584bo4o.cloudfront.net/ublock/localized.svg)](https://crowdin.com/project/ublock) [![License](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://github.com/gorhill/uBlock/blob/master/LICENSE.txt) *** -##### BEWARE! uBlock Origin is COMPLETELY UNRELATED to the web site ublock.org +##### BEWARE! uBlock Origin is (and has always been) COMPLETELY UNRELATED to the web site ublock.org The donations sought by the [individual](https://github.com/chrisaljoudi/) behind `ublock.org` (_"to keeps uBlock development possible"_, [a misrepresentation](https://en.wikipedia.org/wiki/UBlock_Origin#uBlock_.28ublock.org.29)) are _not_ benefiting any of those who contributed most to create uBlock Origin ([developers](https://github.com/gorhill/uBlock/graphs/contributors), [translators](https://crowdin.com/project/ublock), and all those who put efforts in opening detailed issues). For the differences between uBlock Origin and uBlock, see the unbiased [Wikipedia article](https://en.wikipedia.org/wiki/UBlock_Origin). @@ -66,6 +66,8 @@ uBlock Origin Visit the [uBlock Origin's wiki](https://github.com/gorhill/uBlock/wiki) for documentation. +For support/questions/help, there is [/r/uBlockOrigin](https://www.reddit.com/r/uBlockOrigin/) on Reddit. + ## Philosophy uBlock Origin (or uBlock₀) is not an *ad blocker*; it's a general-purpose blocker. uBlock₀ blocks ads through its support of the [Adblock Plus filter syntax](https://adblockplus.org/en/filters). uBlock₀ [extends](https://github.com/gorhill/uBlock/wiki/Filter-syntax-extensions) the syntax and is designed to work with custom rules and filters. Furthermore, advanced mode allows uBlock₀ to work in [default-deny mode](https://github.com/gorhill/uBlock/wiki/Dynamic-filtering:-default-deny), which mode will cause [all 3rd-party network requests](https://requestpolicycontinued.github.io/#what-are-cross-site-requests) to be blocked by default, unless allowed by the user. @@ -122,16 +124,12 @@ You can install the latest version [manually](https://github.com/gorhill/uBlock/ It is expected that uBlock Origin is compatible with any Chromium-based browsers. -**Important:** Chromium-based browsers do not relay [websocket connections](https://en.wikipedia.org/wiki/WebSocket) to the extension API. This means websites can use websocket connections to bypass uBO (or any other blocker). This can be remediated by installing uBO's companion extension [uBO-Extra](https://github.com/gorhill/uBO-Extra). - #### Firefox / Firefox for Android [Firefox Add-ons web site](https://addons.mozilla.org/addon/ublock-origin/). There is also a development version if you want to test uBlock Origin with the latest changes: see [_uBlock Origin Version History_](https://addons.mozilla.org/addon/ublock-origin/versions/beta) uBlock Origin is compatible with [SeaMonkey](http://www.seamonkey-project.org/), [Pale Moon](https://www.palemoon.org/), and possibly other browsers based on Firefox. -The Firefox version of uBlock Origin has [an extra feature](https://github.com/gorhill/uBlock/wiki/Inline-script-tag-filtering) currently not yet available on Chromium-based browsers -- which feature is of great help to foil attempts by many web sites to circumvent blockers. - Also of interest: [Deploying uBlock Origin for Firefox with CCK2 and Group Policy](http://decentsecurity.com/ublock-for-firefox-deployment/). Thanks to Debian contributor [Sean Whitton](https://wiki.debian.org/SeanWhitton), users of Debian 9 or later or Ubuntu 16.04 or later may simply @@ -153,7 +151,7 @@ Development version available at ` - Build the plugin: - Chromium: `./tools/make-chromium.sh` - - Firefox: `./tools/make-firefox.sh all` + - Firefox webext: `./tools/make-webext.sh all` + - Firefox legacy: `./tools/make-firefox.sh all` - Load the result of the build into your browser: - Chromium: load the unpacked extension folder `/uBlock/dist/build/uBlock0.chromium/` in Chromium to use the extension. - - Firefox: drag-and-drop `/uBlock/dist/build/uBlock0.firefox.xpi` into Firefox. + - Firefox: drag-and-drop `/uBlock/dist/build/uBlock0.firefox.xpi` or `/uBlock/dist/build/uBlock0.webext.xpi` into Firefox. diff --git a/dist/description/description-ca.txt b/dist/description/description-ca.txt index dd83d6d98..5966fd2b4 100644 --- a/dist/description/description-ca.txt +++ b/dist/description/description-ca.txt @@ -1,4 +1,4 @@ -Un bloquejador eficient: el consum de memòria i de processador és baix però, no obstant això, pot carregar i aplicar milers de filtres més que altres bloquejadors coneguts. +Un blocador eficient: Amb un consum discret de memòria i de processador, pot carregar i aplicar milers de filtres més que altres aplicacions semblants. Gràfic de l'eficiència: https://github.com/gorhill/uBlock/wiki/uBlock-vs.-ABP:-efficiency-compared diff --git a/dist/description/description-sr.txt b/dist/description/description-sr.txt index 0a201b0c3..84f7c96bb 100644 --- a/dist/description/description-sr.txt +++ b/dist/description/description-sr.txt @@ -1,4 +1,4 @@ -Ефикасан блокатор: ниски процесорски и меморијски захтеви и може учитати и применити хиљаде филтера више него остали популарни блокатори. +Ефикасан блокатор: ниски процесорски и меморијски захтеви а може учитати и применити хиљаде филтера више него остали популарни блокатори. Илустровани преглед његове ефикасности: https://github.com/gorhill/uBlock/wiki/uBlock-vs.-ABP:-efficiency-compared @@ -6,7 +6,7 @@ *** -Флексибилан је, више је од блокатора реклама: може читати и стварати филтере из хост датотека. +Флексибилан је, више је од блокатора реклама: може читати и креирати филтере из хост датотека. Одмах по инсталирању, следећи спискови филтера су учитани и спроведени: @@ -30,7 +30,7 @@ *** -Без предефинисаних спискова филтера, ово проширење је ништа. Тако да ако икад желите да допринесете нешто, размислите о људима који напорно раде одржавајући спискове филтера које користите и који су доступни за бесплатно коришћење свима. +Без предефинисаних спискова филтера, ово проширење је ништа. Тако да ако икад желите да допринесете нечим, размислите о људима који напорно раде одржавајући спискове филтера које користите и који су доступни за бесплатно коришћење свима. *** @@ -45,5 +45,5 @@ Ово је рана верзија, имајте то на уму када будете оцењивали. -Листа измена: +Евиденција промена: https://github.com/gorhill/uBlock/releases diff --git a/dist/description/description-tr.txt b/dist/description/description-tr.txt index ce9b49b4b..b0debbe30 100644 --- a/dist/description/description-tr.txt +++ b/dist/description/description-tr.txt @@ -1,13 +1,13 @@ -Etkili bir engelleyici: Belleği ve işlemciyi yormaz, yine de diğer popüler engelleyicilere göre binlerce daha fazla süzgeci yükleyip uygulayabilir. +Etkili bir engelleyici: Belleği ve işlemciyi yormaz, yine de diğer popüler engelleyicilere göre binlerce daha çok süzgeci yükleyip uygulayabilir. Verimliliğine örneklendirilmiş genel bakış: https://github.com/gorhill/uBlock/wiki/uBlock-vs.-ABP:-efficiency-compared -Kullanımı: Arayüzdeki büyük güç düğmesi mevcut web sitesinde uBlock'u kalıcı olarak etkisiz/etkin kılmak içindir. Bu yalnızca mevcut web sitesine uygulanır, evrensel bir güç düğmesi değildir. +Kullanımı: Arayüzdeki büyük güç düğmesi o anki web sitesinde, uBlock'u kalıcı olarak devre dışı bırakmak/etkinleştirmek içindir. Bu yalnızca o anki web sitesine uygulanır, evrensel bir güç düğmesi değildir. *** -Esnek, bir "reklam engelleyici"den daha fazlası: Alan adları dosyalarınızdan süzgeçleri okuyabilir ve oluşturabilir. +Esnek, bir "reklam engelleyici"den daha fazlası: Ayrıca alan adları dosyalarınızdan süzgeçleri okuyabilir ve oluşturabilir. Hazır olarak şu süzgeç listeleri yüklüdür ve uygulanır: @@ -23,21 +23,21 @@ Hazır olarak şu süzgeç listeleri yüklüdür ve uygulanır: - hpHosts'un Reklam ve izleyici sunucuları - MVPS HOSTS - Spam404 -- Ve daha birçoğu +- Ve daha başkaları -Tabii ki, daha fazla süzgeç etkinleştirildikçe, bellek kullanımı da yükselir. Ama, Fanboy'un iki ekstra listesi, hpHosts'un reklam ve izleyici sunucuları ekledikten sonra dahi uBlock diğer oldukça popüler olan engelleyicilere göre daha az bellek kullanır. +Elbette, daha çok süzgeç etkinleştirildikçe, bellek kullanımı da artar. Yine de, Fanboy'un iki ekstra listesi, hpHosts'un reklam ve izleyici sunucuları ekledikten sonra bile uBlock diğer oldukça popüler engelleyicilere göre daha az bellek kullanır. Ayrıca, bazı ekstra listelerin seçilmesinin web sitelerinin bozulması olasılığını artırabileceğini unutmayın -- özellikle normalde alan adları dosyası olarak kullanılan listelerin. *** -Ön yüklü gelen süzgeç listeleri olmadan, bu eklenti hiçbir işe yaramaz. Eğer gerçekten bir şekilde katkıda bulunmak isterseniz, herkes tarafından özgürce kullanıma imkan veren, kullandığınız süzgeç listelerini oluşturmak için uğraşan insanları düşünün. +Ön yüklü gelen süzgeç listeleri olmadan, bu eklenti bir işe yaramaz. Bu yüzden, gerçekten bir şekilde katkıda bulunmak isterseniz, herkesin özgürce kullanması için sunulan kullandığınız süzgeç listelerini oluşturmak için uğraşan insanları düşünün. *** Özgür. Açık kaynak kamu lisanslı (GPLv3) -Kullanıcılar tarafından kullanıcılar için. +Kullanıcılardan kullanıcılara. Katkıda bulunanlar @ Github: https://github.com/gorhill/uBlock/graphs/contributors Katkıda bulunanlar @ Crowdin: https://crowdin.net/project/ublock diff --git a/platform/chromium/is-webrtc-supported.html b/platform/chromium/is-webrtc-supported.html index 15c155583..d30b674b1 100644 --- a/platform/chromium/is-webrtc-supported.html +++ b/platform/chromium/is-webrtc-supported.html @@ -1,6 +1,7 @@ + diff --git a/platform/chromium/manifest.json b/platform/chromium/manifest.json index 7d5b3871d..9520912ea 100644 --- a/platform/chromium/manifest.json +++ b/platform/chromium/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "uBlock Origin", - "version": "1.13.4", + "version": "1.14.8", "commands": { "launch-element-zapper": { diff --git a/platform/chromium/options_ui.html b/platform/chromium/options_ui.html index d94c001ef..d9e1df5b9 100644 --- a/platform/chromium/options_ui.html +++ b/platform/chromium/options_ui.html @@ -1,6 +1,7 @@ + diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 6f8540865..68ee1af15 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -47,6 +47,22 @@ var noopFunc = function(){}; /******************************************************************************/ +if ( + typeof browser === 'object' && + browser !== null && + browser.runtime instanceof Object && + typeof browser.runtime.getBrowserInfo === 'function' +) { + browser.runtime.getBrowserInfo().then(function(info) { + vAPI.supportsUserStylesheets = + info.name === 'Firefox' && + parseInt(info.version, 10) > 52; + + }); +} + +/******************************************************************************/ + vAPI.app = { name: manifest.name, version: manifest.version @@ -327,7 +343,9 @@ vAPI.tabs.registerListeners = function() { }; var onActivated = function(details) { - vAPI.contextMenu.onMustUpdate(details.tabId); + if ( vAPI.contextMenu instanceof Object ) { + vAPI.contextMenu.onMustUpdate(details.tabId); + } }; var onUpdated = function(tabId, changeInfo, tab) { @@ -478,12 +496,12 @@ vAPI.tabs.open = function(details) { var targetURLWithoutHash = pos === -1 ? targetURL : targetURL.slice(0, pos); chrome.tabs.query({ url: targetURLWithoutHash }, function(tabs) { - var tab = tabs[0]; + if ( chrome.runtime.lastError ) { /* noop */ } + var tab = Array.isArray(tabs) && tabs[0]; if ( !tab ) { wrapper(); return; } - var _details = { active: true, url: undefined @@ -608,32 +626,71 @@ vAPI.tabs.injectScript = function(tabId, details, callback) { // Since we may be called asynchronously, the tab id may not exist // anymore, so this ensures it does still exist. -vAPI.setIcon = function(tabId, iconStatus, badge) { - tabId = toChromiumTabId(tabId); - if ( tabId === 0 ) { - return; - } +// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/browserAction#Browser_compatibility +// Firefox for Android does no support browser.browserAction.setIcon(). - var onIconReady = function() { - if ( vAPI.lastError() ) { - return; +vAPI.setIcon = (function() { + var browserAction = chrome.browserAction, + titleTemplate = chrome.runtime.getManifest().name + ' ({badge})'; + var iconPaths = [ + { + '19': 'img/browsericons/icon19-off.png', + '38': 'img/browsericons/icon38-off.png' + }, + { + '19': 'img/browsericons/icon19.png', + '38': 'img/browsericons/icon38.png' } - chrome.browserAction.setBadgeText({ tabId: tabId, text: badge }); - if ( badge !== '' ) { - chrome.browserAction.setBadgeBackgroundColor({ + ]; + + return function(tabId, iconStatus, badge) { + tabId = toChromiumTabId(tabId); + if ( tabId === 0 ) { return; } + + if ( browserAction.setIcon !== undefined ) { + browserAction.setIcon( + { + tabId: tabId, + path: iconPaths[iconStatus === 'on' ? 1 : 0] + }, + function onIconReady() { + if ( vAPI.lastError() ) { return; } + chrome.browserAction.setBadgeText({ + tabId: tabId, + text: badge + }); + if ( badge !== '' ) { + chrome.browserAction.setBadgeBackgroundColor({ + tabId: tabId, + color: '#666' + }); + } + } + ); + } + + if ( browserAction.setTitle !== undefined ) { + browserAction.setTitle({ tabId: tabId, - color: '#666' + title: titleTemplate.replace( + '{badge}', + iconStatus === 'on' ? (badge !== '' ? badge : '0') : 'off' + ) }); } + + if ( vAPI.contextMenu instanceof Object ) { + vAPI.contextMenu.onMustUpdate(tabId); + } }; +})(); - var iconPaths = iconStatus === 'on' ? - { '19': 'img/browsericons/icon19.png', '38': 'img/browsericons/icon38.png' } : - { '19': 'img/browsericons/icon19-off.png', '38': 'img/browsericons/icon38-off.png' }; - - chrome.browserAction.setIcon({ tabId: tabId, path: iconPaths }, onIconReady); - vAPI.contextMenu.onMustUpdate(tabId); -}; +chrome.browserAction.onClicked.addListener(function(tab) { + vAPI.tabs.open({ + select: true, + url: 'popup.html?tabId=' + tab.id + '&mobile=1' + }); +}); /******************************************************************************/ /******************************************************************************/ @@ -655,8 +712,8 @@ vAPI.messaging.listen = function(listenerName, callback) { /******************************************************************************/ vAPI.messaging.onPortMessage = (function() { - var messaging = vAPI.messaging; - var toAuxPending = {}; + var messaging = vAPI.messaging, + toAuxPending = {}; // Use a wrapper to avoid closure and to allow reuse. var CallbackWrapper = function(port, request, timeout) { @@ -703,8 +760,8 @@ vAPI.messaging.onPortMessage = (function() { }; var toAux = function(details, portFrom) { - var port, portTo; - var chromiumTabId = toChromiumTabId(details.toTabId); + var port, portTo, + chromiumTabId = toChromiumTabId(details.toTabId); // TODO: This could be an issue with a lot of tabs: easy to address // with a port name to tab id map. @@ -761,6 +818,32 @@ vAPI.messaging.onPortMessage = (function() { wrapper.callback(details.msg); }; + var toFramework = function(msg, sender) { + var tabId = sender && sender.tab && sender.tab.id; + if ( !tabId ) { return; } + switch ( msg.what ) { + case 'userCSS': + var details = { + code: undefined, + frameId: sender.frameId, + matchAboutBlank: true + }; + if ( vAPI.supportsUserStylesheets === true ) { + details.cssOrigin = 'user'; + } + if ( msg.toRemove ) { + details.code = msg.toRemove; + chrome.tabs.removeCSS(tabId, details); + } + if ( msg.toAdd ) { + details.code = msg.toAdd; + details.runAt = 'document_start'; + chrome.tabs.insertCSS(tabId, details); + } + break; + } + }; + return function(request, port) { // Auxiliary process to auxiliary process if ( request.toTabId !== undefined ) { @@ -774,6 +857,13 @@ vAPI.messaging.onPortMessage = (function() { return; } + // Content process to main process: framework handler. + // No callback supported/needed for now. + if ( request.channelName === 'vapi-background' ) { + toFramework(request.msg, port.sender); + return; + } + // Auxiliary process to main process: prepare response var callback = messaging.NOOPFUNC; if ( request.auxProcessId !== undefined ) { @@ -781,8 +871,8 @@ vAPI.messaging.onPortMessage = (function() { } // Auxiliary process to main process: specific handler - var r = messaging.UNHANDLED; - var listener = messaging.listeners[request.channelName]; + var r = messaging.UNHANDLED, + listener = messaging.listeners[request.channelName]; if ( typeof listener === 'function' ) { r = listener(request.msg, port.sender, callback); } @@ -856,296 +946,10 @@ vAPI.messaging.broadcast = function(message) { /******************************************************************************/ /******************************************************************************/ -vAPI.net = {}; +// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/contextMenus#Browser_compatibility +// Firefox for Android does no support browser.contextMenus. -/******************************************************************************/ - -vAPI.net.registerListeners = function() { - var µb = µBlock, - µburi = µb.URI, - wrApi = chrome.webRequest; - - // https://bugs.chromium.org/p/chromium/issues/detail?id=410382 - // Between Chromium 38-48, plug-ins' network requests were reported as - // type "other" instead of "object". - var is_v38_48 = /\bChrom[a-z]+\/(?:3[89]|4[0-8])\.[\d.]+\b/.test(navigator.userAgent); - - // 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 - }; - // modern Chromium/WebExtensions: more types available. - if ( wrApi.ResourceType ) { - (function() { - for ( var typeKey in wrApi.ResourceType ) { - if ( wrApi.ResourceType.hasOwnProperty(typeKey) ) { - validTypes[wrApi.ResourceType[typeKey]] = true; - } - } - })(); - } - - var extToTypeMap = new Map([ - ['eot','font'],['otf','font'],['svg','font'],['ttf','font'],['woff','font'],['woff2','font'], - ['mp3','media'],['mp4','media'],['webm','media'], - ['gif','image'],['ico','image'],['jpeg','image'],['jpg','image'],['png','image'],['webp','image'] - ]); - - var denormalizeTypes = function(aa) { - if ( aa.length === 0 ) { - return Object.keys(validTypes); - } - var out = []; - var i = aa.length, - type, - needOther = true; - while ( i-- ) { - type = aa[i]; - if ( validTypes[type] ) { - out.push(type); - } - if ( type === 'other' ) { - needOther = false; - } - } - if ( needOther ) { - out.push('other'); - } - return out; - }; - - var headerValue = function(headers, name) { - var i = headers.length; - while ( i-- ) { - if ( headers[i].name.toLowerCase() === name ) { - return headers[i].value.trim(); - } - } - return ''; - }; - - var normalizeRequestDetails = function(details) { - details.tabId = details.tabId.toString(); - - var type = details.type; - - // https://github.com/gorhill/uBlock/issues/1493 - // Chromium 49+/WebExtensions support a new request type: `ping`, - // which is fired as a result of using `navigator.sendBeacon`. - if ( type === 'ping' ) { - details.type = 'beacon'; - return; - } - - if ( type === 'imageset' ) { - details.type = 'image'; - return; - } - - // The rest of the function code is to normalize type - if ( type !== 'other' ) { - return; - } - - // Try to map known "extension" part of URL to request type. - var path = µburi.pathFromURI(details.url), - pos = path.indexOf('.', path.length - 6); - if ( pos !== -1 && (type = extToTypeMap.get(path.slice(pos + 1))) ) { - details.type = type; - return; - } - - // Try to extract type from response headers if present. - if ( details.responseHeaders ) { - type = headerValue(details.responseHeaders, 'content-type'); - if ( type.startsWith('font/') ) { - details.type = 'font'; - return; - } - if ( type.startsWith('image/') ) { - details.type = 'image'; - return; - } - if ( type.startsWith('audio/') || type.startsWith('video/') ) { - details.type = 'media'; - return; - } - } - - // https://github.com/chrisaljoudi/uBlock/issues/862 - // If no transposition possible, transpose to `object` as per - // Chromium bug 410382 - // https://code.google.com/p/chromium/issues/detail?id=410382 - if ( is_v38_48 ) { - details.type = 'object'; - } - }; - - // https://bugs.chromium.org/p/chromium/issues/detail?id=129353 - // https://github.com/gorhill/uBlock/issues/1497 - // Expose websocket-based network requests to uBO's filtering engine, - // logger, etc. - // Counterpart of following block of code is found in "vapi-client.js" -- - // search for "https://github.com/gorhill/uBlock/issues/1497". - // - // Once uBO 1.11.1 and uBO-Extra 2.12 are widespread, the image-based - // handling code can be removed. - var onBeforeWebsocketRequest = function(details) { - if ( (details.type !== 'image') && - (details.method !== 'HEAD' || details.type !== 'xmlhttprequest') - ) { - return; - } - var requestURL = details.url, - matches = /[?&]u(?:rl)?=([^&]+)/.exec(requestURL); - if ( matches === null ) { return; } - details.type = 'websocket'; - details.url = decodeURIComponent(matches[1]); - var r = onBeforeRequestClient(details); - if ( r && r.cancel ) { return r; } - // Redirect to the provided URL, or a 1x1 data: URI if none provided. - matches = /[?&]r=([^&]+)/.exec(requestURL); - return { - redirectUrl: matches !== null ? - decodeURIComponent(matches[1]) : - 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' - }; - }; - - var onBeforeRequestClient = this.onBeforeRequest.callback; - var onBeforeRequest = validTypes.websocket - // modern Chromium/WebExtensions: type 'websocket' is supported - ? function(details) { - normalizeRequestDetails(details); - return onBeforeRequestClient(details); - } - // legacy Chromium - : function(details) { - // https://github.com/gorhill/uBlock/issues/1497 - if ( details.url.endsWith('ubofix=f41665f3028c7fd10eecf573336216d3') ) { - var r = onBeforeWebsocketRequest(details); - if ( r !== undefined ) { return r; } - } - normalizeRequestDetails(details); - return onBeforeRequestClient(details); - }; - - // This is needed for Chromium 49-55. - var onBeforeSendHeaders = validTypes.csp_report - // modern Chromium/WebExtensions: type 'csp_report' is supported - ? null - // legacy Chromium - : function(details) { - if ( details.type !== 'ping' || details.method !== 'POST' ) { return; } - var type = headerValue(details.requestHeaders, 'content-type'); - if ( type === '' ) { return; } - if ( type.endsWith('/csp-report') ) { - details.type = 'csp_report'; - return onBeforeRequestClient(details); - } - }; - - var onHeadersReceivedClient = this.onHeadersReceived.callback, - onHeadersReceivedClientTypes = this.onHeadersReceived.types.slice(0), - onHeadersReceivedTypes = denormalizeTypes(onHeadersReceivedClientTypes); - var onHeadersReceived = validTypes.font - // modern Chromium/WebExtensions: type 'font' is supported - ? function(details) { - normalizeRequestDetails(details); - if ( - onHeadersReceivedClientTypes.length !== 0 && - onHeadersReceivedClientTypes.indexOf(details.type) === -1 - ) { - return; - } - return onHeadersReceivedClient(details); - } - // legacy Chromium - : function(details) { - normalizeRequestDetails(details); - // Hack to work around Chromium API limitations, where requests of - // type `font` are returned as `other`. For example, our normalization - // fail at transposing `other` into `font` for URLs which are outside - // what is expected. At least when headers are received we can check - // for content type `font/*`. Blocking at onHeadersReceived time is - // less worse than not blocking at all. Also, due to Chromium bug, - // `other` always becomes `object` when it can't be normalized into - // something else. Test case for "unfriendly" font URLs: - // https://www.google.com/fonts - if ( details.type === 'font' ) { - var r = onBeforeRequestClient(details); - if ( typeof r === 'object' && r.cancel === true ) { - return { cancel: true }; - } - } - if ( - onHeadersReceivedClientTypes.length !== 0 && - onHeadersReceivedClientTypes.indexOf(details.type) === -1 - ) { - return; - } - return onHeadersReceivedClient(details); - }; - - var urls, types; - - if ( onBeforeRequest ) { - urls = this.onBeforeRequest.urls || ['']; - types = this.onBeforeRequest.types || undefined; - if ( - (validTypes.websocket) && - (types === undefined || types.indexOf('websocket') !== -1) && - (urls.indexOf('') === -1) - ) { - if ( urls.indexOf('ws://*/*') === -1 ) { - urls.push('ws://*/*'); - } - if ( urls.indexOf('wss://*/*') === -1 ) { - urls.push('wss://*/*'); - } - } - wrApi.onBeforeRequest.addListener( - onBeforeRequest, - { urls: urls, types: types }, - this.onBeforeRequest.extra - ); - } - - // Chromium 48 and lower does not support `ping` type. - // Chromium 56 and higher does support `csp_report` stype. - if ( onBeforeSendHeaders ) { - wrApi.onBeforeSendHeaders.addListener( - onBeforeSendHeaders, - { - 'urls': [ '' ], - 'types': [ 'ping' ] - }, - [ 'blocking', 'requestHeaders' ] - ); - } - - if ( onHeadersReceived ) { - urls = this.onHeadersReceived.urls || ['']; - types = onHeadersReceivedTypes; - wrApi.onHeadersReceived.addListener( - onHeadersReceived, - { urls: urls, types: types }, - this.onHeadersReceived.extra - ); - } -}; - -/******************************************************************************/ -/******************************************************************************/ - -vAPI.contextMenu = { +vAPI.contextMenu = chrome.contextMenus && { _callback: null, _entries: [], _createEntry: function(entry) { @@ -1271,7 +1075,7 @@ vAPI.punycodeURL = function(url) { // https://github.com/gorhill/uBlock/issues/900 // Also, UC Browser: http://www.upsieutoc.com/image/WXuH -vAPI.adminStorage = { +vAPI.adminStorage = chrome.storage.managed && { getItem: function(key, callback) { var onRead = function(store) { var data; @@ -1317,7 +1121,7 @@ vAPI.cloud = (function() { var options = { defaultDeviceName: window.navigator.platform, - deviceName: window.localStorage.getItem('deviceName') || '' + deviceName: vAPI.localStorage.getItem('deviceName') || '' }; // This is used to find out a rough count of how many chunks exists: @@ -1461,7 +1265,7 @@ vAPI.cloud = (function() { } if ( typeof details.deviceName === 'string' ) { - window.localStorage.setItem('deviceName', details.deviceName); + vAPI.localStorage.setItem('deviceName', details.deviceName); options.deviceName = details.deviceName; } diff --git a/platform/chromium/vapi-client.js b/platform/chromium/vapi-client.js index 39768c15f..cd3a7a23a 100644 --- a/platform/chromium/vapi-client.js +++ b/platform/chromium/vapi-client.js @@ -72,70 +72,6 @@ if ( vAPI.sessionId ) { /******************************************************************************/ -var referenceCounter = 0; - -vAPI.lock = function() { - referenceCounter += 1; -}; - -vAPI.unlock = function() { - referenceCounter -= 1; - if ( referenceCounter === 0 ) { - // Eventually there will be code here to flush the javascript code - // from this file out of memory when it ends up unused. - - } -}; - -/******************************************************************************/ - -vAPI.executionCost = { - start: function(){}, - stop: function(){} -}; -/* -vAPI.executionCost = { - tcost: 0, - tstart: 0, - nstart: 0, - level: 1, - start: function() { - if ( this.nstart === 0 ) { - this.tstart = window.performance.now(); - } - this.nstart += 1; - }, - stop: function(mark) { - this.nstart -= 1; - if ( this.nstart !== 0 ) { - return; - } - var tcost = window.performance.now() - this.tstart; - this.tcost += tcost; - if ( mark === undefined ) { - return; - } - var top = window === window.top; - if ( !top && this.level < 2 ) { - return; - } - var context = window === window.top ? ' top' : 'frame'; - var percent = this.tcost / window.performance.now() * 100; - console.log( - 'uBO cost (%s): %sms/%s%% (%s: %sms)', - context, - this.tcost.toFixed(1), - percent.toFixed(1), - mark, - tcost.toFixed(2) - ); - } -}; -*/ -vAPI.executionCost.start(); - -/******************************************************************************/ - vAPI.randomToken = function() { return String.fromCharCode(Date.now() % 26 + 97) + Math.floor(Math.random() * 982451653 + 982451653).toString(36); @@ -412,8 +348,6 @@ vAPI.shutdown.add(function() { // https://www.youtube.com/watch?v=rT5zCHn0tsg // https://www.youtube.com/watch?v=E-jS4e3zacI -vAPI.executionCost.stop('vapi-client.js'); - /******************************************************************************/ /******************************************************************************/ diff --git a/platform/chromium/vapi-common.js b/platform/chromium/vapi-common.js index 5a2c27993..f49f5e420 100644 --- a/platform/chromium/vapi-common.js +++ b/platform/chromium/vapi-common.js @@ -67,12 +67,6 @@ vAPI.download = function(details) { /******************************************************************************/ -vAPI.insertHTML = function(node, html) { - node.innerHTML = html; -}; - -/******************************************************************************/ - vAPI.getURL = chrome.runtime.getURL; /******************************************************************************/ @@ -100,6 +94,26 @@ try { } catch (ex) { } +// https://github.com/gorhill/uBlock/issues/2824 +// Use a dummy localStorage if for some reasons it's not available. +if ( vAPI.localStorage instanceof Object === false ) { + vAPI.localStorage = { + length: 0, + clear: function() { + }, + getItem: function() { + return null; + }, + key: function() { + throw new RangeError(); + }, + removeItem: function() { + }, + setItem: function() { + } + }; +} + /******************************************************************************/ })(this); diff --git a/platform/chromium/vapi-webrequest.js b/platform/chromium/vapi-webrequest.js new file mode 100644 index 000000000..ea9a5e139 --- /dev/null +++ b/platform/chromium/vapi-webrequest.js @@ -0,0 +1,313 @@ +/******************************************************************************* + + 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 +*/ + +// For background page + +'use strict'; + +/******************************************************************************/ + +vAPI.net = {}; + +vAPI.net.registerListeners = function() { + + var µb = µBlock, + µburi = µb.URI, + wrApi = chrome.webRequest; + + // https://bugs.chromium.org/p/chromium/issues/detail?id=410382 + // Between Chromium 38-48, plug-ins' network requests were reported as + // type "other" instead of "object". + var is_v38_48 = /\bChrom[a-z]+\/(?:3[89]|4[0-8])\.[\d.]+\b/.test(navigator.userAgent); + + // 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 + }; + // modern Chromium/WebExtensions: more types available. + if ( wrApi.ResourceType ) { + (function() { + for ( var typeKey in wrApi.ResourceType ) { + if ( wrApi.ResourceType.hasOwnProperty(typeKey) ) { + validTypes[wrApi.ResourceType[typeKey]] = true; + } + } + })(); + } + + var extToTypeMap = new Map([ + ['eot','font'],['otf','font'],['svg','font'],['ttf','font'],['woff','font'],['woff2','font'], + ['mp3','media'],['mp4','media'],['webm','media'], + ['gif','image'],['ico','image'],['jpeg','image'],['jpg','image'],['png','image'],['webp','image'] + ]); + + var denormalizeTypes = function(aa) { + if ( aa.length === 0 ) { + return Object.keys(validTypes); + } + var out = []; + var i = aa.length, + type, + needOther = true; + while ( i-- ) { + type = aa[i]; + if ( validTypes[type] ) { + out.push(type); + } + if ( type === 'other' ) { + needOther = false; + } + } + if ( needOther ) { + out.push('other'); + } + return out; + }; + + var headerValue = function(headers, name) { + var i = headers.length; + while ( i-- ) { + if ( headers[i].name.toLowerCase() === name ) { + return headers[i].value.trim(); + } + } + return ''; + }; + + var normalizeRequestDetails = function(details) { + details.tabId = details.tabId.toString(); + + var type = details.type; + + // https://github.com/gorhill/uBlock/issues/1493 + // Chromium 49+/WebExtensions support a new request type: `ping`, + // which is fired as a result of using `navigator.sendBeacon`. + if ( type === 'ping' ) { + details.type = 'beacon'; + return; + } + + if ( type === 'imageset' ) { + details.type = 'image'; + return; + } + + // The rest of the function code is to normalize type + if ( type !== 'other' ) { + return; + } + + // Try to map known "extension" part of URL to request type. + var path = µburi.pathFromURI(details.url), + pos = path.indexOf('.', path.length - 6); + if ( pos !== -1 && (type = extToTypeMap.get(path.slice(pos + 1))) ) { + details.type = type; + return; + } + + // Try to extract type from response headers if present. + if ( details.responseHeaders ) { + type = headerValue(details.responseHeaders, 'content-type'); + if ( type.startsWith('font/') ) { + details.type = 'font'; + return; + } + if ( type.startsWith('image/') ) { + details.type = 'image'; + return; + } + if ( type.startsWith('audio/') || type.startsWith('video/') ) { + details.type = 'media'; + return; + } + } + + // https://github.com/chrisaljoudi/uBlock/issues/862 + // If no transposition possible, transpose to `object` as per + // Chromium bug 410382 + // https://code.google.com/p/chromium/issues/detail?id=410382 + if ( is_v38_48 ) { + details.type = 'object'; + } + }; + + // https://bugs.chromium.org/p/chromium/issues/detail?id=129353 + // https://github.com/gorhill/uBlock/issues/1497 + // Expose websocket-based network requests to uBO's filtering engine, + // logger, etc. + // Counterpart of following block of code is found in "vapi-client.js" -- + // search for "https://github.com/gorhill/uBlock/issues/1497". + // + // Once uBO 1.11.1 and uBO-Extra 2.12 are widespread, the image-based + // handling code can be removed. + var onBeforeWebsocketRequest = function(details) { + if ( (details.type !== 'image') && + (details.method !== 'HEAD' || details.type !== 'xmlhttprequest') + ) { + return; + } + var requestURL = details.url, + matches = /[?&]u(?:rl)?=([^&]+)/.exec(requestURL); + if ( matches === null ) { return; } + details.type = 'websocket'; + details.url = decodeURIComponent(matches[1]); + var r = onBeforeRequestClient(details); + if ( r && r.cancel ) { return r; } + // Redirect to the provided URL, or a 1x1 data: URI if none provided. + matches = /[?&]r=([^&]+)/.exec(requestURL); + return { + redirectUrl: matches !== null ? + decodeURIComponent(matches[1]) : + 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' + }; + }; + + var onBeforeRequestClient = this.onBeforeRequest.callback; + var onBeforeRequest = validTypes.websocket + // modern Chromium/WebExtensions: type 'websocket' is supported + ? function(details) { + normalizeRequestDetails(details); + return onBeforeRequestClient(details); + } + // legacy Chromium + : function(details) { + // https://github.com/gorhill/uBlock/issues/1497 + if ( details.url.endsWith('ubofix=f41665f3028c7fd10eecf573336216d3') ) { + var r = onBeforeWebsocketRequest(details); + if ( r !== undefined ) { return r; } + } + normalizeRequestDetails(details); + return onBeforeRequestClient(details); + }; + + // This is needed for Chromium 49-55. + var onBeforeSendHeaders = validTypes.csp_report + // modern Chromium/WebExtensions: type 'csp_report' is supported + ? null + // legacy Chromium + : function(details) { + if ( details.type !== 'ping' || details.method !== 'POST' ) { return; } + var type = headerValue(details.requestHeaders, 'content-type'); + if ( type === '' ) { return; } + if ( type.endsWith('/csp-report') ) { + details.type = 'csp_report'; + return onBeforeRequestClient(details); + } + }; + + var onHeadersReceivedClient = this.onHeadersReceived.callback, + onHeadersReceivedClientTypes = this.onHeadersReceived.types.slice(0), + onHeadersReceivedTypes = denormalizeTypes(onHeadersReceivedClientTypes); + var onHeadersReceived = validTypes.font + // modern Chromium/WebExtensions: type 'font' is supported + ? function(details) { + normalizeRequestDetails(details); + if ( + onHeadersReceivedClientTypes.length !== 0 && + onHeadersReceivedClientTypes.indexOf(details.type) === -1 + ) { + return; + } + return onHeadersReceivedClient(details); + } + // legacy Chromium + : function(details) { + normalizeRequestDetails(details); + // Hack to work around Chromium API limitations, where requests of + // type `font` are returned as `other`. For example, our normalization + // fail at transposing `other` into `font` for URLs which are outside + // what is expected. At least when headers are received we can check + // for content type `font/*`. Blocking at onHeadersReceived time is + // less worse than not blocking at all. Also, due to Chromium bug, + // `other` always becomes `object` when it can't be normalized into + // something else. Test case for "unfriendly" font URLs: + // https://www.google.com/fonts + if ( details.type === 'font' ) { + var r = onBeforeRequestClient(details); + if ( typeof r === 'object' && r.cancel === true ) { + return { cancel: true }; + } + } + if ( + onHeadersReceivedClientTypes.length !== 0 && + onHeadersReceivedClientTypes.indexOf(details.type) === -1 + ) { + return; + } + return onHeadersReceivedClient(details); + }; + + var urls, types; + + if ( onBeforeRequest ) { + urls = this.onBeforeRequest.urls || ['']; + types = this.onBeforeRequest.types || undefined; + if ( + (validTypes.websocket) && + (types === undefined || types.indexOf('websocket') !== -1) && + (urls.indexOf('') === -1) + ) { + if ( urls.indexOf('ws://*/*') === -1 ) { + urls.push('ws://*/*'); + } + if ( urls.indexOf('wss://*/*') === -1 ) { + urls.push('wss://*/*'); + } + } + wrApi.onBeforeRequest.addListener( + onBeforeRequest, + { urls: urls, types: types }, + this.onBeforeRequest.extra + ); + } + + // Chromium 48 and lower does not support `ping` type. + // Chromium 56 and higher does support `csp_report` stype. + if ( onBeforeSendHeaders ) { + wrApi.onBeforeSendHeaders.addListener( + onBeforeSendHeaders, + { + 'urls': [ '' ], + 'types': [ 'ping' ] + }, + [ 'blocking', 'requestHeaders' ] + ); + } + + if ( onHeadersReceived ) { + urls = this.onHeadersReceived.urls || ['']; + types = onHeadersReceivedTypes; + wrApi.onHeadersReceived.addListener( + onHeadersReceived, + { urls: urls, types: types }, + this.onHeadersReceived.extra + ); + } +}; + +/******************************************************************************/ diff --git a/platform/firefox/install.rdf b/platform/firefox/install.rdf index 8ead14129..c9b9320f8 100644 --- a/platform/firefox/install.rdf +++ b/platform/firefox/install.rdf @@ -21,7 +21,7 @@ {{ec8030f7-c20a-464f-9b0e-13a3a9e97384}} 24.0 - * + 57.0a1 @@ -30,7 +30,7 @@ {{aa3c5121-dab2-40e2-81ca-7ea25febc110}} 27.0 - * + 57.0a1 diff --git a/platform/firefox/vapi-background.js b/platform/firefox/vapi-background.js index b5901ec8b..40157849f 100644 --- a/platform/firefox/vapi-background.js +++ b/platform/firefox/vapi-background.js @@ -680,6 +680,12 @@ var winWatcher = (function() { if ( !win || windowToIdMap.delete(win) !== true ) { return; } + // https://github.com/uBlockOrigin/uAssets/issues/567 + // We need to cleanup if and only if the window being closed is + // the actual top window. + if ( win.gBrowser && win.gBrowser.ownerGlobal !== win ) { + return; + } if ( typeof api.onCloseWindow === 'function' ) { api.onCloseWindow(win); } @@ -3125,6 +3131,7 @@ vAPI.toolbarButton = { '#' + this.viewId + ',', '#' + this.viewId + ' > iframe {', 'height: 290px;', + 'max-width: none !important;', 'min-width: 0 !important;', 'overflow: hidden !important;', 'padding: 0 !important;', diff --git a/platform/firefox/vapi-client.js b/platform/firefox/vapi-client.js index 5ec47c0eb..22205299a 100644 --- a/platform/firefox/vapi-client.js +++ b/platform/firefox/vapi-client.js @@ -54,63 +54,6 @@ var vAPI = self.vAPI; /******************************************************************************/ -var referenceCounter = 0; - -vAPI.lock = function() { - referenceCounter += 1; -}; - -vAPI.unlock = function() { - referenceCounter -= 1; -}; - -/******************************************************************************/ - -vAPI.executionCost = { - start: function(){}, - stop: function(){} -}; -/* -vAPI.executionCost = vAPI.executionCost || { - tcost: 0, - tstart: 0, - nstart: 0, - level: 1, - start: function() { - if ( this.nstart === 0 ) { - this.tstart = window.performance.now(); - } - this.nstart += 1; - }, - stop: function(mark) { - this.nstart -= 1; - if ( this.nstart !== 0 ) { - return; - } - var tcost = window.performance.now() - this.tstart; - this.tcost += tcost; - if ( mark === undefined ) { - return; - } - var top = window === window.top; - if ( !top && this.level < 2 ) { - return; - } - var context = window === window.top ? ' top' : 'frame'; - var percent = this.tcost / window.performance.now() * 100; - console.log( - 'uBO cost (' + context + '): ' + - this.tcost.toFixed(1) + 'ms/' + - percent.toFixed(1) + '% (' + - mark + ': ' + tcost.toFixed(2) + 'ms)' - ); - } -}; -*/ -vAPI.executionCost.start(); - -/******************************************************************************/ - vAPI.firefox = true; vAPI.randomToken = function() { @@ -498,10 +441,6 @@ if ( window !== window.top ) { /******************************************************************************/ -vAPI.executionCost.stop('vapi-client.js'); - -/******************************************************************************/ - })(this); /******************************************************************************/ diff --git a/platform/firefox/vapi-common.js b/platform/firefox/vapi-common.js index 1ee769987..d5b0abae3 100644 --- a/platform/firefox/vapi-common.js +++ b/platform/firefox/vapi-common.js @@ -75,30 +75,6 @@ vAPI.download = function(details) { /******************************************************************************/ -vAPI.insertHTML = (function() { - const parser = Components.classes['@mozilla.org/parserutils;1'] - .getService(Components.interfaces.nsIParserUtils); - - // https://github.com/gorhill/uBlock/issues/845 - // Apparently dashboard pages execute with `about:blank` principal. - - return function(node, html) { - while ( node.firstChild ) { - node.removeChild(node.firstChild); - } - - node.appendChild(parser.parseFragment( - html, - parser.SanitizerAllowStyle, - false, - Services.io.newURI('about:blank', null, null), - document.documentElement - )); - }; -})(); - -/******************************************************************************/ - vAPI.getURL = function(path) { return 'chrome://' + location.host + '/content/' + path.replace(/^\/+/, ''); }; diff --git a/platform/opera/manifest.json b/platform/opera/manifest.json index 17b2bd97f..38e4df96a 100644 --- a/platform/opera/manifest.json +++ b/platform/opera/manifest.json @@ -1,16 +1,5 @@ { - "manifest_version": 2, - - "name": "uBlock Origin", - "version": "1.9.15.101", - - "default_locale": "en", - "description": "__MSG_extShortDesc__", - "icons": { - "16": "img/icon_16.png", - "128": "img/icon_128.png" - }, - + "author": "All uBlock Origin contributors", "browser_action": { "default_icon": { "19": "img/browsericons/icon19.png", @@ -19,11 +8,20 @@ "default_title": "uBlock Origin", "default_popup": "popup.html" }, - - "author": "All uBlock Origin contributors", "background": { "page": "background.html" }, + "commands": { + "launch-element-zapper": { + "description": "__MSG_popupTipZapper__" + }, + "launch-element-picker": { + "description": "__MSG_popupTipPicker__" + }, + "launch-logger": { + "description": "__MSG_popupTipLog__" + } + }, "content_scripts": [ { "matches": ["http://*/*", "https://*/*"], @@ -38,8 +36,16 @@ "all_frames": false } ], + "default_locale": "en", + "description": "__MSG_extShortDesc__", + "icons": { + "16": "img/icon_16.png", + "128": "img/icon_128.png" + }, "incognito": "split", - "minimum_chrome_version": "38.0", + "manifest_version": 2, + "minimum_opera_version": "25.0", + "name": "uBlock Origin", "optional_permissions": [ "file:///*" ], "options_page": "dashboard.html", "permissions": [ @@ -53,5 +59,6 @@ "webRequestBlocking", "" ], - "short_name": "uBlock₀" + "short_name": "uBlock₀", + "version": "1.9.15.101" } diff --git a/platform/webext/background.html b/platform/webext/background.html index 659797740..1f84280da 100644 --- a/platform/webext/background.html +++ b/platform/webext/background.html @@ -10,6 +10,7 @@ + diff --git a/platform/webext/bootstrap.js b/platform/webext/bootstrap.js index ebbbc0b78..3f770948e 100644 --- a/platform/webext/bootstrap.js +++ b/platform/webext/bootstrap.js @@ -29,8 +29,8 @@ const hostName = 'ublock0'; /******************************************************************************/ -function startup({ webExtension }) { - webExtension.startup().then(api => { +function startup({ webExtension }, reason) { + webExtension.startup(reason).then(api => { let { browser } = api, storageMigrator; let onMessage = function(message, sender, callback) { diff --git a/platform/webext/from-legacy.js b/platform/webext/from-legacy.js index 93edba2be..cf0fd4927 100644 --- a/platform/webext/from-legacy.js +++ b/platform/webext/from-legacy.js @@ -27,21 +27,31 @@ (function() { let µb = µBlock; + let migratedKeys = new Set(); + let reCacheStorageKeys = /^(?:assetCacheRegistry|assetSourceRegistry|cache\/.+|selfie)$/; let migrateAll = function(callback) { - let mustRestart = false; - let migrateKeyValue = function(details, callback) { + // https://github.com/gorhill/uBlock/issues/2653 + // Be ready to deal graciously with corrupted DB. + if ( migratedKeys.has(details.key) ) { + callback(); + return; + } + migratedKeys.add(details.key); let bin = {}; bin[details.key] = JSON.parse(details.value); - self.browser.storage.local.set(bin, callback); - mustRestart = true; + if ( reCacheStorageKeys.test(details.key) ) { + vAPI.cacheStorage.set(bin, callback); + } else { + vAPI.storage.set(bin, callback); + } }; let migrateNext = function() { self.browser.runtime.sendMessage({ what: 'webext:storageMigrateNext' }, response => { if ( response.key === undefined ) { - if ( mustRestart ) { + if ( migratedKeys.size !== 0 ) { self.browser.runtime.reload(); } else { callback(); @@ -57,7 +67,7 @@ self.browser.runtime.sendMessage({ what: 'webext:storageMigrateDone' }); return callback(); } - self.browser.storage.local.set({ legacyStorageMigrated: true }); + vAPI.storage.set({ legacyStorageMigrated: true }); migrateNext(); }); }; diff --git a/platform/webext/install.rdf b/platform/webext/install.rdf index 48ba61afc..cc8a8b258 100644 --- a/platform/webext/install.rdf +++ b/platform/webext/install.rdf @@ -1,7 +1,7 @@ - uBlock0-webext@raymondhill.net + uBlock0@raymondhill.net {version} {name} {description} @@ -20,8 +20,8 @@ {{ec8030f7-c20a-464f-9b0e-13a3a9e97384}} - 52.0a1 - * + 54.0 + 56.* @@ -30,7 +30,7 @@ {{aa3c5121-dab2-40e2-81ca-7ea25febc110}} 54.0 - * + 56.* diff --git a/platform/webext/manifest.json b/platform/webext/manifest.json index 72d1a14e2..8e4100e62 100644 --- a/platform/webext/manifest.json +++ b/platform/webext/manifest.json @@ -1,84 +1,81 @@ { - "manifest_version": 2, - - "name": "uBlock Origin", - "version": "1.9.15.101", - - "applications": { - "gecko": { - "id": "uBlock0@raymondhill.net", - "strict_min_version": "52.0a1" - } - }, - - "commands": { - "launch-element-zapper": { - "suggested_key": { - "default": "Alt+Z" - }, - "description": "__MSG_popupTipZapper__" - }, - "launch-element-picker": { - "suggested_key": { - "default": "Alt+X" - }, - "description": "__MSG_popupTipPicker__" - }, - "launch-logger": { - "suggested_key": { - "default": "Alt+L" - }, - "description": "__MSG_popupTipLog__" - } - }, - "default_locale": "en", - "description": "__MSG_extShortDesc__", - "icons": { - "16": "img/icon_16.png", - "128": "img/icon_128.png" - }, - - "browser_action": { - "browser_style": false, - "default_icon": { - "19": "img/browsericons/icon19.png", - "38": "img/browsericons/icon38.png" - }, - "default_title": "uBlock Origin", - "default_popup": "popup.html" - }, - - "author": "All uBlock Origin contributors", - "background": { - "page": "background.html" - }, - "content_scripts": [ - { - "matches": ["http://*/*", "https://*/*"], - "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 - } - ], - "minimum_chrome_version": "26.0", - "options_ui": { - "page": "options_ui.html" - }, - "permissions": [ - "contextMenus", - "privacy", - "storage", - "tabs", - "webNavigation", - "webRequest", - "webRequestBlocking", - "" - ], - "short_name": "uBlock₀" + "applications":{ + "gecko":{ + "id":"uBlock0@raymondhill.net", + "strict_min_version":"52.0" + } + }, + "author":"All uBlock Origin contributors", + "background":{ + "page":"background.html" + }, + "browser_action":{ + "browser_style":false, + "default_icon":{ + "19":"img/browsericons/icon19.png", + "38":"img/browsericons/icon38.png" + }, + "default_title":"uBlock Origin", + "default_popup":"popup.html" + }, + "commands":{ + "launch-element-zapper":{ + "description":"__MSG_popupTipZapper__" + }, + "launch-element-picker":{ + "description":"__MSG_popupTipPicker__" + }, + "launch-logger":{ + "description":"__MSG_popupTipLog__" + } + }, + "content_scripts":[ + { + "matches":[ + "http://*/*", + "https://*/*" + ], + "js":[ + "js/vapi-client.js", + "js/vapi-usercss.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" + }, + "manifest_version":2, + "name":"uBlock Origin", + "options_ui":{ + "page":"options_ui.html" + }, + "permissions":[ + "contextMenus", + "privacy", + "storage", + "tabs", + "webNavigation", + "webRequest", + "webRequestBlocking", + "" + ], + "short_name":"uBlock₀", + "version":"1.9.15.101" } diff --git a/platform/webext/options_ui.html b/platform/webext/options_ui.html new file mode 100644 index 000000000..300959392 --- /dev/null +++ b/platform/webext/options_ui.html @@ -0,0 +1,13 @@ + + + + + + + +
+ + + + + diff --git a/platform/webext/vapi-cachestorage.js b/platform/webext/vapi-cachestorage.js new file mode 100644 index 000000000..180a8c32c --- /dev/null +++ b/platform/webext/vapi-cachestorage.js @@ -0,0 +1,269 @@ +/******************************************************************************* + + uBlock Origin - a browser extension to block requests. + Copyright (C) 2016-2017 The uBlock Origin authors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see {http://www.gnu.org/licenses/}. + + Home: https://github.com/gorhill/uBlock +*/ + +/* global indexedDB, IDBDatabase */ + +'use strict'; + +/******************************************************************************/ + +// The code below has been originally manually imported from: +// Commit: https://github.com/nikrolls/uBlock-Edge/commit/d1538ea9bea89d507219d3219592382eee306134 +// Commit date: 29 October 2016 +// Commit author: https://github.com/nikrolls +// Commit message: "Implement cacheStorage using IndexedDB" + +// The original imported code has been subsequently modified as it was not +// compatible with Firefox. +// (a Promise thing, see https://github.com/dfahlander/Dexie.js/issues/317) +// Furthermore, code to migrate from browser.storage.local to vAPI.cacheStorage +// has been added, for seamless migration of cache-related entries into +// indexedDB. + +vAPI.cacheStorage = (function() { + const STORAGE_NAME = 'uBlock0CacheStorage'; + var db; + var pending = []; + + // prime the db so that it's ready asap for next access. + getDb(noopfn); + + return { get, set, remove, clear, getBytesInUse }; + + function get(input, callback) { + if ( typeof callback !== 'function' ) { return; } + if ( input === null ) { + return getAllFromDb(callback); + } + var toRead, output = {}; + if ( typeof input === 'string' ) { + toRead = [ input ]; + } else if ( Array.isArray(input) ) { + toRead = input; + } else /* if ( typeof input === 'object' ) */ { + toRead = Object.keys(input); + output = input; + } + return getFromDb(toRead, output, callback); + } + + function set(input, callback) { + putToDb(input, callback); + } + + function remove(key, callback) { + deleteFromDb(key, callback); + } + + function clear(callback) { + clearDb(callback); + } + + function getBytesInUse(keys, callback) { + // TODO: implement this + callback(0); + } + + function genericErrorHandler(error) { + console.error('[uBlock0 cacheStorage]', error); + } + + function noopfn() { + } + + function processPendings() { + var cb; + while ( (cb = pending.shift()) ) { + cb(db); + } + } + + function getDb(callback) { + if ( pending === undefined ) { + return callback(); + } + if ( pending.length !== 0 ) { + return pending.push(callback); + } + if ( db instanceof IDBDatabase ) { + return callback(db); + } + pending.push(callback); + if ( pending.length !== 1 ) { return; } + // This will fail in private browsing mode. + var req = indexedDB.open(STORAGE_NAME, 1); + req.onupgradeneeded = function(ev) { + db = ev.target.result; + db.onerror = genericErrorHandler; + var table = db.createObjectStore(STORAGE_NAME, { keyPath: 'key' }); + table.createIndex('value', 'value', { unique: false }); + }; + req.onsuccess = function(ev) { + db = ev.target.result; + db.onerror = genericErrorHandler; + processPendings(); + }; + req.onerror = function() { + console.log(this.error); + processPendings(); + pending = undefined; + }; + } + + function getFromDb(keys, store, callback) { + if ( typeof callback !== 'function' ) { return; } + if ( keys.length === 0 ) { return callback(store); } + var notfoundKeys = new Set(keys); + var gotOne = function() { + if ( typeof this.result === 'object' ) { + store[this.result.key] = this.result.value; + notfoundKeys.delete(this.result.key); + } + }; + getDb(function(db) { + if ( !db ) { return callback(); } + var transaction = db.transaction(STORAGE_NAME); + transaction.oncomplete = transaction.onerror = function() { + // TODO: remove once storage.local is clean + if ( notfoundKeys.size === 0 ) { + vAPI.storage.remove(keys); + return callback(store); + } + maybeMigrate(Array.from(notfoundKeys), store, callback); + }; + var table = transaction.objectStore(STORAGE_NAME); + for ( var key of keys ) { + var req = table.get(key); + req.onsuccess = gotOne; + req.onerror = noopfn; + } + }); + } + + // Migrate from storage API + // TODO: removes once all users are migrated to the new cacheStorage. + function maybeMigrate(keys, store, callback) { + var toMigrate = new Set(), + i = keys.length; + while ( i-- ) { + var key = keys[i]; + toMigrate.add(key); + // If migrating a compiled list, also migrate the non-compiled + // counterpart. + if ( /^cache\/compiled\//.test(key) ) { + toMigrate.add(key.replace('/compiled', '')); + } + } + vAPI.storage.get(Array.from(toMigrate), function(bin) { + if ( bin instanceof Object === false ) { + return callback(store); + } + var migratedKeys = Object.keys(bin); + if ( migratedKeys.length === 0 ) { + return callback(store); + } + var i = migratedKeys.length; + while ( i-- ) { + var key = migratedKeys[i]; + store[key] = bin[key]; + } + vAPI.storage.remove(migratedKeys); + vAPI.cacheStorage.set(bin); + callback(store); + }); + } + + function getAllFromDb(callback) { + if ( typeof callback !== 'function' ) { + callback = noopfn; + } + getDb(function(db) { + if ( !db ) { return callback(); } + var output = {}; + var transaction = db.transaction(STORAGE_NAME); + transaction.oncomplete = transaction.onerror = function() { + callback(output); + }; + var table = transaction.objectStore(STORAGE_NAME), + req = table.openCursor(); + req.onsuccess = function(ev) { + var cursor = ev.target.result; + if ( !cursor ) { return; } + output[cursor.key] = cursor.value; + cursor.continue(); + }; + }); + } + + function putToDb(input, callback) { + if ( typeof callback !== 'function' ) { + callback = noopfn; + } + var keys = Object.keys(input); + if ( keys.length === 0 ) { return callback(); } + getDb(function(db) { + if ( !db ) { return callback(); } + var transaction = db.transaction(STORAGE_NAME, 'readwrite'); + transaction.oncomplete = transaction.onerror = callback; + var table = transaction.objectStore(STORAGE_NAME), + entry = {}; + for ( var key of keys ) { + entry.key = key; + entry.value = input[key]; + table.put(entry); + } + }); + } + + function deleteFromDb(input, callback) { + if ( typeof callback !== 'function' ) { + callback = noopfn; + } + var keys = Array.isArray(input) ? input.slice() : [ input ]; + if ( keys.length === 0 ) { return callback(); } + getDb(function(db) { + if ( !db ) { return callback(); } + var transaction = db.transaction(STORAGE_NAME, 'readwrite'); + transaction.oncomplete = transaction.onerror = callback; + var table = transaction.objectStore(STORAGE_NAME); + for ( var key of keys ) { + table.delete(key); + } + }); + // TODO: removes once all users are migrated to the new cacheStorage. + vAPI.storage.remove(keys); + } + + function clearDb(callback) { + if ( typeof callback !== 'function' ) { + callback = noopfn; + } + getDb(function(db) { + if ( !db ) { return callback(); } + var req = db.transaction(STORAGE_NAME, 'readwrite') + .objectStore(STORAGE_NAME) + .clear(); + req.onsuccess = req.onerror = callback; + }); + } +}()); + +/******************************************************************************/ diff --git a/platform/webext/vapi-usercss.js b/platform/webext/vapi-usercss.js new file mode 100644 index 000000000..aea2a2bf5 --- /dev/null +++ b/platform/webext/vapi-usercss.js @@ -0,0 +1,77 @@ +/******************************************************************************* + + 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'; + +// For content pages + +/******************************************************************************/ + +(function() { + if ( typeof vAPI !== 'object' ) { return; } + + vAPI.userCSS = { + _userCSS: '', + _disabled: false, + _send: function(toRemove, toAdd) { + vAPI.messaging.send('vapi-background', { + what: 'userCSS', + toRemove: toRemove, + toAdd: toAdd + }); + }, + add: function(cssText) { + if ( cssText === '' ) { return; } + var before = this._userCSS, + after = before; + if ( after !== '' ) { after += '\n'; } + after += cssText; + this._userCSS = after; + if ( this._disabled ) { return; } + this._send(before, after); + }, + remove: function(cssText) { + if ( cssText === '' || this._userCSS === '' ) { return; } + var before = this._userCSS, + after = before; + after = before.replace(cssText, '').trim(); + this._userCSS = after; + if ( this._disabled ) { return; } + this._send(before, after); + }, + toggle: function(state) { + if ( state === undefined ) { + state = this._disabled; + } + if ( state !== this._disabled ) { return; } + this._disabled = !state; + if ( this._userCSS === '' ) { return; } + var toAdd, toRemove; + if ( state ) { + toAdd = this._userCSS; + } else { + toRemove = this._userCSS; + } + this._send(toRemove, toAdd); + } + }; + vAPI.hideNode = vAPI.unhideNode = function(){}; +})(); diff --git a/platform/webext/vapi-webrequest.js b/platform/webext/vapi-webrequest.js new file mode 100644 index 000000000..7945bd485 --- /dev/null +++ b/platform/webext/vapi-webrequest.js @@ -0,0 +1,182 @@ +/******************************************************************************* + + 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 +*/ + +// For background page + +'use strict'; + +/******************************************************************************/ + +vAPI.net = {}; + +vAPI.net.registerListeners = function() { + + // https://github.com/gorhill/uBlock/issues/2950 + // Firefox 55 does not normalize URLs to ASCII, uBO must do this itself. + // https://bugzilla.mozilla.org/show_bug.cgi?id=945240 + var mustPunycode = false; + (function() { + if ( + typeof browser === 'object' && + browser !== null && + browser.runtime instanceof Object && + typeof browser.runtime.getBrowserInfo === 'function' + ) { + browser.runtime.getBrowserInfo().then(info => { + mustPunycode = info.name === 'Firefox' && + /^5[0-6]\./.test(info.version); + }); + } + })(); + + 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 + }; + // 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; + } + } + } + + var denormalizeTypes = function(aa) { + if ( aa.length === 0 ) { + return Object.keys(validTypes); + } + var out = []; + var i = aa.length, + type, + needOther = true; + while ( i-- ) { + type = aa[i]; + if ( validTypes[type] ) { + out.push(type); + } + if ( type === 'other' ) { + needOther = false; + } + } + if ( needOther ) { + out.push('other'); + } + return out; + }; + + var punycode = self.punycode; + var reMustNormalizeHostname = /[^0-9a-z._-]/; + var parsedURL = new URL('about:blank'); + + var normalizeRequestDetails = function(details) { + details.tabId = details.tabId.toString(); + + if ( + mustPunycode === true && + reMustNormalizeHostname.test(details.url) === true + ) { + parsedURL.href = details.url; + details.url = details.url.replace( + parsedURL.hostname, + punycode.toASCII(parsedURL.hostname) + ); + } + + var type = details.type; + + // https://github.com/gorhill/uBlock/issues/1493 + // Chromium 49+/WebExtensions support a new request type: `ping`, + // which is fired as a result of using `navigator.sendBeacon`. + if ( type === 'ping' ) { + details.type = 'beacon'; + return; + } + + if ( type === 'imageset' ) { + details.type = 'image'; + return; + } + }; + + var onBeforeRequestClient = this.onBeforeRequest.callback; + var onBeforeRequest = function(details) { + normalizeRequestDetails(details); + return onBeforeRequestClient(details); + }; + + var onHeadersReceivedClient = this.onHeadersReceived.callback, + onHeadersReceivedClientTypes = this.onHeadersReceived.types.slice(0), + onHeadersReceivedTypes = denormalizeTypes(onHeadersReceivedClientTypes); + var onHeadersReceived = function(details) { + normalizeRequestDetails(details); + if ( + onHeadersReceivedClientTypes.length !== 0 && + onHeadersReceivedClientTypes.indexOf(details.type) === -1 + ) { + return; + } + return onHeadersReceivedClient(details); + }; + + if ( onBeforeRequest ) { + let urls = this.onBeforeRequest.urls || ['']; + let types = this.onBeforeRequest.types || undefined; + if ( + (validTypes.websocket) && + (types === undefined || types.indexOf('websocket') !== -1) && + (urls.indexOf('') === -1) + ) { + if ( urls.indexOf('ws://*/*') === -1 ) { + urls.push('ws://*/*'); + } + if ( urls.indexOf('wss://*/*') === -1 ) { + urls.push('wss://*/*'); + } + } + wrApi.onBeforeRequest.addListener( + onBeforeRequest, + { urls: urls, types: types }, + this.onBeforeRequest.extra + ); + } + + if ( onHeadersReceived ) { + let urls = this.onHeadersReceived.urls || ['']; + let types = onHeadersReceivedTypes; + wrApi.onHeadersReceived.addListener( + onHeadersReceived, + { urls: urls, types: types }, + this.onHeadersReceived.extra + ); + } +}; + +/******************************************************************************/ diff --git a/src/3p-filters.html b/src/3p-filters.html index 714052ece..062b24fab 100644 --- a/src/3p-filters.html +++ b/src/3p-filters.html @@ -45,7 +45,7 @@ + diff --git a/src/cloud-ui.html b/src/cloud-ui.html new file mode 100644 index 000000000..0d11edeab --- /dev/null +++ b/src/cloud-ui.html @@ -0,0 +1,20 @@ + + + + + + + + + +  + + +
+
+

+

+

+
+ + diff --git a/src/css/about.css b/src/css/about.css deleted file mode 100644 index 20d78cbf7..000000000 --- a/src/css/about.css +++ /dev/null @@ -1,4 +0,0 @@ -ul { - padding-__MSG_@@bidi_start_edge__: 1em; - margin-__MSG_@@bidi_start_edge__: 1em; - } diff --git a/src/css/logger-ui.css b/src/css/logger-ui.css index a1a371b5c..355c2f11e 100644 --- a/src/css/logger-ui.css +++ b/src/css/logger-ui.css @@ -578,12 +578,12 @@ body.colorBlind #netFilteringDialog .dialog > div.containers > div.dynamic tr.e #filterFinderDialog .dialog { padding: 1em; + word-break: break-all; } #filterFinderDialog .dialog code { background: #eee; font-size: 85%; padding: 3px; - word-break: break-all; } #filterFinderDialog .dialog ul { font-size: larger; diff --git a/src/css/popup.css b/src/css/popup.css index fb8769897..12b0b8e1d 100644 --- a/src/css/popup.css +++ b/src/css/popup.css @@ -193,20 +193,24 @@ body[dir="ltr"] #extraTools > span > span.badge { body[dir="rtl"] #extraTools > span > span.badge { right: 100%; } -#extraTools > span.on > span:last-of-type { +#extraTools > span > span:last-of-type { color: #e00; - font-size: 1.1em; - left: 0; + left: 50%; position: absolute; - text-align: center; top: 0; - width: 100%; + transform: translateX(-50%); + visibility: hidden; } -#extraTools > span.on > span:last-of-type:after { - content: '\2715'; +#extraTools > span > span:last-of-type > svg { + stroke: red; + stroke-width: 2; + width: 1em; + } +#extraTools > span.on > span:last-of-type { + visibility: visible; } #extraTools > span:hover { - color: #444; + color: #333; } #refresh { diff --git a/src/document-blocked.html b/src/document-blocked.html index ca24ee7b1..40c03aa3b 100644 --- a/src/document-blocked.html +++ b/src/document-blocked.html @@ -1,6 +1,7 @@ +