From a15d6164fa76b14a06de23eecaa850704a77447f Mon Sep 17 00:00:00 2001 From: Alessio Vanni Date: Thu, 4 Jul 2019 17:12:55 +0200 Subject: Split context menu from vapi-background Also fix a typo in Makefile. --- js/vapi-contextmenu.js | 219 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 js/vapi-contextmenu.js (limited to 'js/vapi-contextmenu.js') diff --git a/js/vapi-contextmenu.js b/js/vapi-contextmenu.js new file mode 100644 index 0000000..28715a8 --- /dev/null +++ b/js/vapi-contextmenu.js @@ -0,0 +1,219 @@ +/******************************************************************************* + + ηMatrix - a browser extension to black/white list requests. + Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors + Copyright (C) 2019 Alessio Vanni + + 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://gitlab.com/vannilla/ematrix + uMatrix Home: https://github.com/gorhill/uMatrix +*/ + +'use strict'; + +/******************************************************************************/ + +(function () { + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); + + let vAPI = self.vAPI; // Guaranteed to be initialized by vapi-background.js + + vAPI.contextMenu = { + contextMap: { + frame: 'inFrame', + link: 'onLink', + image: 'onImage', + audio: 'onAudio', + video: 'onVideo', + editable: 'onEditableArea' + } + }; + + vAPI.contextMenu.displayMenuItem = function ({target}) { + let doc = target.ownerDocument; + let gContextMenu = doc.defaultView.gContextMenu; + if (!gContextMenu.browser) { + return; + } + + let menuitem = doc.getElementById(vAPI.contextMenu.menuItemId); + let currentURI = gContextMenu.browser.currentURI; + + // https://github.com/chrisaljoudi/uBlock/issues/105 + // TODO: Should the element picker works on any kind of pages? + if (!currentURI.schemeIs('http') && !currentURI.schemeIs('https')) { + menuitem.setAttribute('hidden', true); + return; + } + + let ctx = vAPI.contextMenu.contexts; + + if (!ctx) { + menuitem.setAttribute('hidden', false); + return; + } + + let ctxMap = vAPI.contextMenu.contextMap; + + for (let context of ctx) { + if (context === 'page' + && !gContextMenu.onLink + && !gContextMenu.onImage + && !gContextMenu.onEditableArea + && !gContextMenu.inFrame + && !gContextMenu.onVideo + && !gContextMenu.onAudio) { + menuitem.setAttribute('hidden', false); + return; + } + + if (ctxMap.hasOwnProperty(context) + && gContextMenu[ctxMap[context]]) { + menuitem.setAttribute('hidden', false); + return; + } + } + + menuitem.setAttribute('hidden', true); + }; + + vAPI.contextMenu.register = (function () { + let register = function (doc) { + if (!this.menuItemId) { + return; + } + + // Already installed? + if (doc.getElementById(this.menuItemId) !== null) { + return; + } + + let contextMenu = doc.getElementById('contentAreaContextMenu'); + + let menuitem = doc.createElement('menuitem'); + menuitem.setAttribute('id', this.menuItemId); + menuitem.setAttribute('label', this.menuLabel); + menuitem.setAttribute('image', vAPI.getURL('img/browsericons/icon19-19.png')); + menuitem.setAttribute('class', 'menuitem-iconic'); + menuitem.addEventListener('command', this.onCommand); + + contextMenu.addEventListener('popupshowing', this.displayMenuItem); + contextMenu.insertBefore(menuitem, doc.getElementById('inspect-separator')); + }; + + var registerSafely = function (doc, tryCount) { + // https://github.com/gorhill/uBlock/issues/906 + // Be sure document.readyState is 'complete': it could happen + // at launch time that we are called by + // vAPI.contextMenu.create() directly before the environment + // is properly initialized. + if (doc.readyState === 'complete') { + register.call(this, doc); + return; + } + + if (typeof tryCount !== 'number') { + tryCount = 0; + } + + tryCount += 1; + if ( tryCount < 8) { + vAPI.setTimeout(registerSafely.bind(this, doc, tryCount), 200); + } + }; + + return registerSafely; + })(); + + vAPI.contextMenu.unregister = function (doc) { + if (!this.menuItemId) { + return; + } + + let menuitem = doc.getElementById(this.menuItemId); + if (menuitem === null) { + return; + } + + let contextMenu = menuitem.parentNode; + menuitem.removeEventListener('command', this.onCommand); + contextMenu.removeEventListener('popupshowing', this.displayMenuItem); + contextMenu.removeChild(menuitem); + }; + + vAPI.contextMenu.create = function (details, callback) { + this.menuItemId = details.id; + this.menuLabel = details.title; + this.contexts = details.contexts; + + console.debug(this.menuItemId); + + if (Array.isArray(this.contexts) && this.contexts.length) { + this.contexts = this.contexts.indexOf('all') === -1 + ? this.contexts + : null; + } else { + // default in Chrome + this.contexts = ['page']; + } + + this.onCommand = function () { + let gContextMenu = vAPI.browser.getOwnerWindow(this).gContextMenu; + let details = { + menuItemId: this.id + }; + + if (gContextMenu.inFrame) { + details.tagName = 'iframe'; + // Probably won't work with e10s + // eMatrix: doesn't matter ;) + details.frameUrl = gContextMenu.focusedWindow.location.href; + } else if (gContextMenu.onImage) { + details.tagName = 'img'; + details.srcUrl = gContextMenu.mediaURL; + } else if (gContextMenu.onAudio) { + details.tagName = 'audio'; + details.srcUrl = gContextMenu.mediaURL; + } else if (gContextMenu.onVideo) { + details.tagName = 'video'; + details.srcUrl = gContextMenu.mediaURL; + } else if (gContextMenu.onLink) { + details.tagName = 'a'; + details.linkUrl = gContextMenu.linkURL; + } + + callback(details, { + id: vAPI.tabs.manager.tabIdFromTarget(gContextMenu.browser), + url: gContextMenu.browser.currentURI.asciiSpec + }); + }; + + for (let win of vAPI.window.getWindows()) { + this.register(win.document); + } + }; + + vAPI.contextMenu.remove = function () { + for (let win of vAPI.window.getWindows()) { + this.unregister(win.document); + } + + this.menuItemId = null; + this.menuLabel = null; + this.contexts = null; + this.onCommand = null; + }; +})(); -- cgit v1.2.3