From 39aeaa12a73eb1de0cacb35238db61b04a1dfd05 Mon Sep 17 00:00:00 2001 From: gorhill Date: Sat, 27 May 2017 11:51:24 -0400 Subject: [PATCH] new feature: element zapper --- platform/chromium/manifest.json | 14 ++++++ platform/chromium/vapi-background.js | 5 ++ platform/webext/background.html | 1 + platform/webext/manifest.json | 14 ++++++ src/_locales/en/messages.json | 4 ++ src/background.html | 1 + src/css/popup.css | 3 +- src/epicker.html | 7 ++- src/js/background.js | 1 + src/js/commands.js | 51 ++++++++++++++++++++ src/js/contextmenu.js | 6 +-- src/js/messaging.js | 5 +- src/js/popup.js | 17 +++++++ src/js/scriptlets/element-picker.js | 69 +++++++++++++++++++++++----- src/js/ublock.js | 3 +- src/js/uritools.js | 8 +++- src/popup.html | 7 +-- 17 files changed, 191 insertions(+), 25 deletions(-) create mode 100644 src/js/commands.js diff --git a/platform/chromium/manifest.json b/platform/chromium/manifest.json index cf05723c1..0ecd0e8ae 100644 --- a/platform/chromium/manifest.json +++ b/platform/chromium/manifest.json @@ -4,6 +4,20 @@ "name": "uBlock Origin", "version": "1.12.5.10", + "commands": { + "launch-element-zapper": { + "suggested_key": { + "default": "Alt+Z" + }, + "description": "__MSG_popupTipZapper__" + }, + "launch-element-picker": { + "suggested_key": { + "default": "Alt+X" + }, + "description": "__MSG_popupTipPicker__" + } + }, "default_locale": "en", "description": "__MSG_extShortDesc__", "icons": { diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 7496f47ba..6f8540865 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -1192,6 +1192,11 @@ vAPI.contextMenu = { /******************************************************************************/ /******************************************************************************/ +vAPI.commands = chrome.commands; + +/******************************************************************************/ +/******************************************************************************/ + vAPI.lastError = function() { return chrome.runtime.lastError; }; diff --git a/platform/webext/background.html b/platform/webext/background.html index 1abcdc511..659797740 100644 --- a/platform/webext/background.html +++ b/platform/webext/background.html @@ -33,5 +33,6 @@ + diff --git a/platform/webext/manifest.json b/platform/webext/manifest.json index a848a03e0..b3f875d89 100644 --- a/platform/webext/manifest.json +++ b/platform/webext/manifest.json @@ -11,6 +11,20 @@ } }, + "commands": { + "launch-element-zapper": { + "suggested_key": { + "default": "Alt+Z" + }, + "description": "__MSG_popupTipZapper__" + }, + "launch-element-picker": { + "suggested_key": { + "default": "Alt+X" + }, + "description": "__MSG_popupTipPicker__" + } + }, "default_locale": "en", "description": "__MSG_extShortDesc__", "icons": { diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 05eb18ade..af9e952c1 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -71,6 +71,10 @@ "message":"Click to 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" diff --git a/src/background.html b/src/background.html index 0d56942fc..219f0390b 100644 --- a/src/background.html +++ b/src/background.html @@ -32,5 +32,6 @@ + diff --git a/src/css/popup.css b/src/css/popup.css index 721c5f5e2..f4303c7a8 100644 --- a/src/css/popup.css +++ b/src/css/popup.css @@ -167,10 +167,11 @@ body.off #switch .fa { color: #aaa; cursor: pointer; display: none; + min-width: 1em; unicode-bidi: embed; } .tool.enabled { - display: inline; + display: inline-block; } .tool:hover { color: #444; diff --git a/src/epicker.html b/src/epicker.html index 42df64141..2339e6058 100644 --- a/src/epicker.html +++ b/src/epicker.html @@ -141,7 +141,7 @@ svg { cursor: not-allowed; } svg > path:first-child { - fill: rgba(0,0,0,0.75); + fill: rgba(0,0,0,0.6); fill-rule: evenodd; } svg > path + path { @@ -149,6 +149,11 @@ svg > path + path { stroke-width: 0.5px; fill: rgba(255,63,63,0.20); } +body.zap svg > path + path { + stroke: #FF0; + stroke-width: 0.5px; + fill: rgba(255,255,63,0.20); +} body.preview svg > path { fill: rgba(0,0,0,0.10); } diff --git a/src/js/background.js b/src/js/background.js index 2c2dcd5d8..78207177b 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -158,6 +158,7 @@ var µBlock = (function() { // jshint ignore:line mouseY: -1, mouseURL: '', epickerTarget: '', + epickerZap: false, epickerEprom: null, scriptlets: { diff --git a/src/js/commands.js b/src/js/commands.js new file mode 100644 index 000000000..0efe9bfad --- /dev/null +++ b/src/js/commands.js @@ -0,0 +1,51 @@ +/******************************************************************************* + + 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'; + +/******************************************************************************/ + +(function() { + if ( vAPI.commands === undefined ) { return; } + + vAPI.commands.onCommand.addListener(function(command) { + var µb = µBlock; + + switch ( command ) { + case 'launch-element-zapper': + case 'launch-element-picker': + vAPI.tabs.get(null, function(tab) { + if ( tab instanceof Object === false ) { + return; + } + µb.mouseX = µb.mouseY = -1; + µb.elementPickerExec(tab.id, undefined, command === 'launch-element-zapper'); + }); + break; + default: + break; + } + }); +})(); + +/******************************************************************************/ diff --git a/src/js/contextmenu.js b/src/js/contextmenu.js index d02c03bb0..9cd2163b0 100644 --- a/src/js/contextmenu.js +++ b/src/js/contextmenu.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2014-2015 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 @@ -19,12 +19,12 @@ Home: https://github.com/gorhill/uBlock */ +'use strict'; + /******************************************************************************/ µBlock.contextMenu = (function() { -'use strict'; - /******************************************************************************/ var µb = µBlock; diff --git a/src/js/messaging.js b/src/js/messaging.js index bd353e4e7..f79a18ac8 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -138,7 +138,7 @@ var onMessage = function(request, sender, callback) { case 'launchElementPicker': // Launched from some auxiliary pages, clear context menu coords. µb.mouseX = µb.mouseY = -1; - µb.elementPickerExec(request.tabId, request.targetURL || ''); + µb.elementPickerExec(request.tabId, request.targetURL, request.zap); break; case 'gotoURL': @@ -324,7 +324,7 @@ var popupDataFromTabId = function(tabId, tabTitle) { r.hostnameDict = getHostnameDict(pageStore.hostnameToCountMap); r.contentLastModified = pageStore.contentLastModified; r.firewallRules = getFirewallRules(rootHostname, r.hostnameDict); - r.canElementPicker = rootHostname.indexOf('.') !== -1; + r.canElementPicker = µb.URI.isNetworkURI(r.rawURL); r.noPopups = µb.hnSwitches.evaluateZ('no-popups', rootHostname); r.popupBlockedCount = pageStore.popupBlockedCount; r.noCosmeticFiltering = µb.hnSwitches.evaluateZ('no-cosmetic-filtering', rootHostname); @@ -606,6 +606,7 @@ var onMessage = function(request, sender, callback) { target: µb.epickerTarget, clientX: µb.mouseX, clientY: µb.mouseY, + zap: µb.epickerZap, eprom: µb.epickerEprom }); diff --git a/src/js/popup.js b/src/js/popup.js index ce17ae5d8..27d15f333 100644 --- a/src/js/popup.js +++ b/src/js/popup.js @@ -408,6 +408,7 @@ var renderPopup = function() { // If you think the `=== true` is pointless, you are mistaken uDom.nodeFromId('gotoPick').classList.toggle('enabled', popupData.canElementPicker === true); + uDom.nodeFromId('gotoZap').classList.toggle('enabled', popupData.canElementPicker === true); var text, blocked = popupData.pageBlockedRequestCount, @@ -587,6 +588,21 @@ var toggleNetFilteringSwitch = function(ev) { /******************************************************************************/ +var gotoZap = function() { + messaging.send( + 'popupPanel', + { + what: 'launchElementPicker', + tabId: popupData.tabId, + zap: true + } + ); + + vAPI.closePopup(); +}; + +/******************************************************************************/ + var gotoPick = function() { messaging.send( 'popupPanel', @@ -981,6 +997,7 @@ var onHideTooltip = function() { getPopupData(tabId); uDom('#switch').on('click', toggleNetFilteringSwitch); + uDom('#gotoZap').on('click', gotoZap); uDom('#gotoPick').on('click', gotoPick); uDom('a[href]').on('click', gotoURL); uDom('h2').on('click', toggleFirewallPane); diff --git a/src/js/scriptlets/element-picker.js b/src/js/scriptlets/element-picker.js index 9c14dfb2f..23fb0abb0 100644 --- a/src/js/scriptlets/element-picker.js +++ b/src/js/scriptlets/element-picker.js @@ -1179,7 +1179,10 @@ var showDialog = function(options) { populate(netFilterCandidates, '#netFilters'); populate(cosmeticFilterCandidates, '#cosmeticFilters'); - dialog.querySelector('ul').style.display = netFilterCandidates.length || cosmeticFilterCandidates.length ? '' : 'none'; + dialog.querySelector('ul').style.display = + netFilterCandidates.length || cosmeticFilterCandidates.length + ? '' + : 'none'; dialog.querySelector('#create').disabled = true; // Auto-select a candidate filter @@ -1201,21 +1204,44 @@ var showDialog = function(options) { /******************************************************************************/ -var elementFromPoint = function(x, y) { - if ( !pickerRoot ) { - return null; +var zap = function() { + if ( targetElements.length === 0 ) { return; } + var elem = targetElements[0], + style = window.getComputedStyle(elem); + // Heuristic to detect scroll-locking: remove such lock when detected. + if ( parseInt(style.zIndex, 10) >= 1000 || style.position === 'fixed' ) { + document.body.style.setProperty('overflow', 'auto', 'important'); + document.documentElement.style.setProperty('overflow', 'auto', 'important'); } - pickerRoot.style.pointerEvents = 'none'; - var elem = document.elementFromPoint(x, y); - if ( elem === document.body || elem === document.documentElement ) { - elem = null; - } - pickerRoot.style.pointerEvents = ''; - return elem; + elem.parentNode.removeChild(elem); }; /******************************************************************************/ +var elementFromPoint = (function() { + var lastX, lastY; + + return function(x, y) { + if ( x !== undefined ) { + lastX = x; lastY = y; + } else if ( lastX !== undefined ) { + x = lastX; y = lastY; + } else { + return null; + } + if ( !pickerRoot ) { return null; } + pickerRoot.style.pointerEvents = 'none'; + var elem = document.elementFromPoint(x, y); + if ( elem === document.body || elem === document.documentElement ) { + elem = null; + } + pickerRoot.style.pointerEvents = ''; + return elem; + }; +})(); + +/******************************************************************************/ + var onSvgHovered = (function() { var timer = null; var mx = 0, my = 0; @@ -1253,6 +1279,11 @@ var onSvgClicked = function(ev) { if ( filtersFrom(ev.clientX, ev.clientY) === 0 ) { return; } + if ( pickerBody.classList.contains('zap') ) { + zap(); + stopPicker(); + return; + } showDialog(); }; @@ -1266,11 +1297,24 @@ var svgListening = function(on) { /******************************************************************************/ var onKeyPressed = function(ev) { - if ( ev.which === 27 ) { + var elem; + + // Delete + if ( ev.key === 'Delete' ) { + ev.stopPropagation(); + ev.preventDefault(); + zap(); + elem = elementFromPoint(); + highlightElements(elem ? [elem] : []); + return; + } + // Esc + if ( ev.key === 'Escape' || ev.which === 27 ) { ev.stopPropagation(); ev.preventDefault(); filterToDOMInterface.preview(false); stopPicker(); + return; } }; @@ -1360,6 +1404,7 @@ var startPicker = function(details) { pickerBody = frameDoc.body; pickerBody.setAttribute('lang', navigator.language); + pickerBody.classList.toggle('zap', details.zap === true); dialog = pickerBody.querySelector('aside'); dialog.addEventListener('click', onDialogClicked); diff --git a/src/js/ublock.js b/src/js/ublock.js index 18c61e5cd..ae28c1226 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -392,11 +392,12 @@ var reInvalidHostname = /[^a-z0-9.\-\[\]:]/, /******************************************************************************/ -µBlock.elementPickerExec = function(tabId, targetElement) { +µBlock.elementPickerExec = function(tabId, targetElement, zap) { if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } this.epickerTarget = targetElement || ''; + this.epickerZap = zap || false; this.scriptlets.inject(tabId, 'element-picker'); if ( typeof vAPI.tabs.select === 'function' ) { vAPI.tabs.select(tabId); diff --git a/src/js/uritools.js b/src/js/uritools.js index 661d6c608..57c4f5177 100644 --- a/src/js/uritools.js +++ b/src/js/uritools.js @@ -403,15 +403,19 @@ URI.domainFromURI = function(uri) { /******************************************************************************/ URI.isNetworkURI = function(uri) { - return /^(?:ftps?|https?|wss?):\/\//.test(uri); + return reNetworkURI.test(uri); }; +var reNetworkURI = /^(?:ftps?|https?|wss?):\/\//; + /******************************************************************************/ URI.isNetworkScheme = function(scheme) { - return /^(?:ftps?|https?|wss?)$/.test(scheme); + return reNetworkScheme.test(scheme); }; +var reNetworkScheme = /^(?:ftps?|https?|wss?)$/; + /******************************************************************************/ // Normalize the way µBlock expects it diff --git a/src/popup.html b/src/popup.html index 88081f16d..ef4627aaa 100644 --- a/src/popup.html +++ b/src/popup.html @@ -16,9 +16,10 @@

 

-    -   - +  

?