From e8572bdc214c816a508591e6a7e40b09601c1bda Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 11 Apr 2015 23:23:22 -0600 Subject: [PATCH] Element picker: cleanup and Firefox improvements (closes #1211) In Firefox, you can now block an element right from the Web Inspector. This was implemented by @AlexVallat -- thanks to Alex for the awesome feature enhancement. --- platform/firefox/vapi-background.js | 67 ++++++++++++++++++++++++++--- src/js/background.js | 3 +- src/js/contextmenu.js | 20 +-------- src/js/element-picker.js | 53 +++++------------------ src/js/messaging.js | 3 +- src/js/ublock.js | 4 +- 6 files changed, 76 insertions(+), 74 deletions(-) diff --git a/platform/firefox/vapi-background.js b/platform/firefox/vapi-background.js index 578523e65..a0d81b173 100644 --- a/platform/firefox/vapi-background.js +++ b/platform/firefox/vapi-background.js @@ -65,7 +65,7 @@ vAPI.app.restart = function() { var cleanupTasks = []; // This must be updated manually, every time a new task is added/removed -var expectedNumberOfCleanups = 6; // 7 instances of cleanupTasks.push, but one is unique to fennec, and one to desktop. +var expectedNumberOfCleanups = vAPI.fennec ? 7 : 8; // 8 instances of cleanupTasks.push, but one is unique to fennec, and two to desktop. window.addEventListener('unload', function() { for ( var cleanup of cleanupTasks ) { @@ -392,8 +392,9 @@ vAPI.tabs.registerListeners = function() { cleanupTasks.push(function() { Services.ww.unregisterNotification(windowWatcher); + vAPI.contextMenu.remove(); + for ( var win of vAPI.tabs.getWindows() ) { - vAPI.contextMenu.unregister(win.document); win.removeEventListener('DOMContentLoaded', windowWatcher.onReady); var tabContainer; @@ -1760,6 +1761,15 @@ vAPI.contextMenu.displayMenuItem = function({target}) { /******************************************************************************/ +vAPI.contextMenu.createContextMenuItem = function(doc) { + var menuitem = doc.createElement('menuitem'); + menuitem.setAttribute('id', this.menuItemId); + menuitem.setAttribute('label', this.menuLabel); + menuitem.setAttribute('image', vAPI.getURL('img/browsericons/icon16.svg')); + menuitem.setAttribute('class', 'menuitem-iconic'); + return menuitem; +} + vAPI.contextMenu.register = function(doc) { if ( !this.menuItemId ) { return; @@ -1777,11 +1787,7 @@ vAPI.contextMenu.register = function(doc) { } var contextMenu = doc.getElementById('contentAreaContextMenu'); - var menuitem = doc.createElement('menuitem'); - menuitem.setAttribute('id', this.menuItemId); - menuitem.setAttribute('label', this.menuLabel); - menuitem.setAttribute('image', vAPI.getURL('img/browsericons/icon16.svg')); - menuitem.setAttribute('class', 'menuitem-iconic'); + var menuitem = this.createContextMenuItem(doc); menuitem.addEventListener('command', this.onCommand); contextMenu.addEventListener('popupshowing', this.displayMenuItem); contextMenu.insertBefore(menuitem, doc.getElementById('inspect-separator')); @@ -1808,6 +1814,36 @@ vAPI.contextMenu.unregister = function(doc) { /******************************************************************************/ +vAPI.contextMenu.registerForWebInspector = function(eventName, toolbox, panel) { + var menuPopup = panel.panelDoc.getElementById("inspector-node-popup"); + var deleteMenuItem = panel.panelDoc.getElementById("node-menu-delete"); + var tiltButton = toolbox.toolboxButtons.filter(tool => tool.id === "command-button-tilt")[0]; + tiltButton = tiltButton && tiltButton.button; + + if (menuPopup && deleteMenuItem) { + var menuitem = vAPI.contextMenu.createContextMenuItem(panel.panelDoc); + menuitem.addEventListener('command', function() { + var selectedNodeFront = panel.selection.nodeFront; + while (selectedNodeFront && selectedNodeFront.baseURI !== panel.walker.rootNode.baseURI) { + // This is an iFrame, so we can't select it directly. Walk up the parent stack until we do + selectedNodeFront = selectedNodeFront.parentNode(); + } + if (selectedNodeFront) { + selectedNodeFront.getUniqueSelector().then(selector => µBlock.elementPickerExec(vAPI.tabs.getTabId(panel.browser), selector)); + + // Turn off 3D view, if it's turned on. + if (tiltButton && tiltButton.checked) { + tiltButton.click(); + } + } + }); + + menuPopup.insertBefore(menuitem, deleteMenuItem); + } +} + +/******************************************************************************/ + vAPI.contextMenu.create = function(details, callback) { this.menuItemId = details.id; this.menuLabel = details.title; @@ -1850,6 +1886,19 @@ vAPI.contextMenu.create = function(details, callback) { }); }; + // Also add a context menu to the web inspector + if (!vAPI.fennec) { + try { + this.gDevTools = Cu.import('resource:///modules/devtools/gDevTools.jsm', null).gDevTools; + } catch (ex) { + // console.error(ex); + } + + if (this.gDevTools) { + this.gDevTools.on("inspector-ready", this.registerForWebInspector); + } + } + for ( var win of vAPI.tabs.getWindows() ) { this.register(win.document); } @@ -1862,6 +1911,10 @@ vAPI.contextMenu.remove = function() { this.unregister(win.document); } + if (!vAPI.fennec && this.gDevTools) { + this.gDevTools.off("inspector-ready", this.registerForWebInspector); + } + this.menuItemId = null; this.menuLabel = null; this.contexts = null; diff --git a/src/js/background.js b/src/js/background.js index 7f2672d40..4d6da24d2 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -136,11 +136,10 @@ return { noopFunc: function(){}, apiErrorCount: 0, - contextMenuTarget: '', contextMenuClientX: -1, contextMenuClientY: -1, - epickerTarget: '', + epickerTargetElementSelector: null, epickerEprom: null, // so that I don't have to care for last comma diff --git a/src/js/contextmenu.js b/src/js/contextmenu.js index 3ef6316af..6b9accfb4 100644 --- a/src/js/contextmenu.js +++ b/src/js/contextmenu.js @@ -45,26 +45,8 @@ var onContextMenuClicked = function(details, tab) { if ( /^https?:\/\//.test(tab.url) === false ) { return; } - var tagName = details.tagName || ''; - var src = details.frameUrl || details.srcUrl || details.linkUrl || ''; - if ( !tagName ) { - if ( typeof details.frameUrl === 'string' ) { - tagName = 'iframe'; - } else if ( typeof details.srcUrl === 'string' ) { - if ( details.mediaType === 'image' ) { - tagName = 'img'; - } else if ( details.mediaType === 'video' ) { - tagName = 'video'; - } else if ( details.mediaType === 'audio' ) { - tagName = 'audio'; - } - } else if ( typeof details.linkUrl === 'string' ) { - tagName = 'a'; - } - } - - µb.elementPickerExec(tab.id, tagName + '\t' + src); + µb.elementPickerExec(tab.id); }; /******************************************************************************/ diff --git a/src/js/element-picker.js b/src/js/element-picker.js index 8e965d24c..0273cda62 100644 --- a/src/js/element-picker.js +++ b/src/js/element-picker.js @@ -128,7 +128,8 @@ if ( window.top !== window ) { var pickerRoot = document.getElementById(vAPI.sessionId); if ( pickerRoot ) { - return; + // If it's already running, stop it and then allow it to restart + pickerRoot.onload(); // Calls stopPicker } var localMessager = vAPI.messaging.channel('element-picker.js'); @@ -873,53 +874,21 @@ var startPicker = function(details) { highlightElements([], true); - var elem; + var elem = null; + + // If a target element was provided, use it + if (details.targetElementSelector) { + elem = document.querySelector(details.targetElementSelector); + } // Try using mouse position - if ( details.clientX !== -1 ) { + if (!elem && details.clientX !== -1) { elem = elementFromPoint(details.clientX, details.clientY); - if ( elem !== null ) { - filtersFromElement(elem); - showDialog(); - return; - } } - // No mouse position available, use suggested target - var target = details.target || ''; - var pos = target.indexOf('\t'); - if ( pos === -1 ) { - return; - } - var srcAttrMap = { - 'a': 'href', - 'img': 'src', - 'iframe': 'src', - 'embed': 'src', - 'video': 'src', - 'audio': 'src' - }; - var tagName = target.slice(0, pos); - var url = target.slice(pos + 1); - var attr = srcAttrMap[tagName]; - if ( attr === undefined ) { - return; - } - var elems = document.querySelectorAll(tagName + '[' + attr + ']'); - var i = elems.length; - var src; - while ( i-- ) { - elem = elems[i]; - src = elem[attr]; - if ( typeof src !== 'string' || src === '' ) { - continue; - } - if ( src !== url ) { - continue; - } + if (elem !== null) { filtersFromElement(elem); - showDialog({ modifier: true }); - return; + showDialog(); } }; diff --git a/src/js/messaging.js b/src/js/messaging.js index 4e526cab7..d2ddcdbc4 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -574,13 +574,12 @@ var onMessage = function(request, sender, callback) { callback({ frameContent: this.responseText.replace(reStrings, replacer), - target: µb.contextMenuTarget, + targetElementSelector: µb.epickerTargetElementSelector, clientX: µb.contextMenuClientX, clientY: µb.contextMenuClientY, eprom: µb.epickerEprom }); - µb.contextMenuTarget = ''; µb.contextMenuClientX = -1; µb.contextMenuClientY = -1; }; diff --git a/src/js/ublock.js b/src/js/ublock.js index 3734de881..b1d53efa9 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -276,8 +276,8 @@ var matchWhitelistDirective = function(url, hostname, directive) { /******************************************************************************/ -µBlock.elementPickerExec = function(tabId, targetElement) { - this.epickerTarget = targetElement || ''; +µBlock.elementPickerExec = function(tabId, targetElementSelector) { + this.epickerTargetElementSelector = targetElementSelector; vAPI.tabs.injectScript(tabId, { file: 'js/element-picker.js' }); };