vapi-contextmenu.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /*******************************************************************************
  2. ηMatrix - a browser extension to black/white list requests.
  3. Copyright (C) 2014-2019 The uMatrix/uBlock Origin authors
  4. Copyright (C) 2019-2022 Alessio Vanni
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 3 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program. If not, see {http://www.gnu.org/licenses/}.
  15. Home: https://gitlab.com/vannilla/ematrix
  16. uMatrix Home: https://github.com/gorhill/uMatrix
  17. */
  18. 'use strict';
  19. /******************************************************************************/
  20. (function () {
  21. vAPI.contextMenu = {
  22. contextMap: {
  23. frame: 'inFrame',
  24. link: 'onLink',
  25. image: 'onImage',
  26. audio: 'onAudio',
  27. video: 'onVideo',
  28. editable: 'onEditableArea'
  29. }
  30. };
  31. vAPI.contextMenu.displayMenuItem = function ({target}) {
  32. let doc = target.ownerDocument;
  33. let gContextMenu = doc.defaultView.gContextMenu;
  34. if (!gContextMenu.browser) {
  35. return;
  36. }
  37. let menuitem = doc.getElementById(vAPI.contextMenu.menuItemId);
  38. let currentURI = gContextMenu.browser.currentURI;
  39. // https://github.com/chrisaljoudi/uBlock/issues/105
  40. // TODO: Should the element picker works on any kind of pages?
  41. if (!currentURI.schemeIs('http') && !currentURI.schemeIs('https')) {
  42. menuitem.setAttribute('hidden', true);
  43. return;
  44. }
  45. let ctx = vAPI.contextMenu.contexts;
  46. if (!ctx) {
  47. menuitem.setAttribute('hidden', false);
  48. return;
  49. }
  50. let ctxMap = vAPI.contextMenu.contextMap;
  51. for (let context of ctx) {
  52. if (context === 'page'
  53. && !gContextMenu.onLink
  54. && !gContextMenu.onImage
  55. && !gContextMenu.onEditableArea
  56. && !gContextMenu.inFrame
  57. && !gContextMenu.onVideo
  58. && !gContextMenu.onAudio) {
  59. menuitem.setAttribute('hidden', false);
  60. return;
  61. }
  62. if (ctxMap.hasOwnProperty(context)
  63. && gContextMenu[ctxMap[context]]) {
  64. menuitem.setAttribute('hidden', false);
  65. return;
  66. }
  67. }
  68. menuitem.setAttribute('hidden', true);
  69. };
  70. vAPI.contextMenu.register = (function () {
  71. let register = function (doc) {
  72. if (!this.menuItemId) {
  73. return;
  74. }
  75. // Already installed?
  76. if (doc.getElementById(this.menuItemId) !== null) {
  77. return;
  78. }
  79. let contextMenu = doc.getElementById('contentAreaContextMenu');
  80. let menuitem = doc.createElement('menuitem');
  81. menuitem.setAttribute('id', this.menuItemId);
  82. menuitem.setAttribute('label', this.menuLabel);
  83. menuitem.setAttribute('image', vAPI.getURL('img/browsericons/icon19-19.png'));
  84. menuitem.setAttribute('class', 'menuitem-iconic');
  85. menuitem.addEventListener('command', this.onCommand);
  86. contextMenu.addEventListener('popupshowing', this.displayMenuItem);
  87. contextMenu.insertBefore(menuitem, doc.getElementById('inspect-separator'));
  88. };
  89. let registerSafely = function (doc, tryCount) {
  90. // https://github.com/gorhill/uBlock/issues/906
  91. // Be sure document.readyState is 'complete': it could happen
  92. // at launch time that we are called by
  93. // vAPI.contextMenu.create() directly before the environment
  94. // is properly initialized.
  95. if (doc.readyState === 'complete') {
  96. register.call(this, doc);
  97. return;
  98. }
  99. if (typeof tryCount !== 'number') {
  100. tryCount = 0;
  101. }
  102. tryCount += 1;
  103. if (tryCount < 8) {
  104. vAPI.setTimeout(registerSafely.bind(this, doc, tryCount), 200);
  105. }
  106. };
  107. return registerSafely;
  108. })();
  109. vAPI.contextMenu.unregister = function (doc) {
  110. if (!this.menuItemId) {
  111. return;
  112. }
  113. let menuitem = doc.getElementById(this.menuItemId);
  114. if (menuitem === null) {
  115. return;
  116. }
  117. let contextMenu = menuitem.parentNode;
  118. menuitem.removeEventListener('command', this.onCommand);
  119. contextMenu.removeEventListener('popupshowing', this.displayMenuItem);
  120. contextMenu.removeChild(menuitem);
  121. };
  122. vAPI.contextMenu.create = function (details, callback) {
  123. this.menuItemId = details.id;
  124. this.menuLabel = details.title;
  125. this.contexts = details.contexts;
  126. if (Array.isArray(this.contexts) && this.contexts.length) {
  127. this.contexts = this.contexts.indexOf('all') === -1
  128. ? this.contexts
  129. : null;
  130. } else {
  131. // default in Chrome
  132. this.contexts = ['page'];
  133. }
  134. this.onCommand = function () {
  135. let gContextMenu = vAPI.browser.getOwnerWindow(this).gContextMenu;
  136. let details = {
  137. menuItemId: this.id
  138. };
  139. if (gContextMenu.inFrame) {
  140. details.tagName = 'iframe';
  141. // Probably won't work with e10s
  142. // eMatrix: doesn't matter ;)
  143. details.frameUrl = gContextMenu.focusedWindow.location.href;
  144. } else if (gContextMenu.onImage) {
  145. details.tagName = 'img';
  146. details.srcUrl = gContextMenu.mediaURL;
  147. } else if (gContextMenu.onAudio) {
  148. details.tagName = 'audio';
  149. details.srcUrl = gContextMenu.mediaURL;
  150. } else if (gContextMenu.onVideo) {
  151. details.tagName = 'video';
  152. details.srcUrl = gContextMenu.mediaURL;
  153. } else if (gContextMenu.onLink) {
  154. details.tagName = 'a';
  155. details.linkUrl = gContextMenu.linkURL;
  156. }
  157. callback(details, {
  158. id: vAPI.tabs.manager.tabIdFromTarget(gContextMenu.browser),
  159. url: gContextMenu.browser.currentURI.asciiSpec
  160. });
  161. };
  162. for (let win of vAPI.window.getWindows()) {
  163. this.register(win.document);
  164. }
  165. };
  166. vAPI.contextMenu.remove = function () {
  167. for (let win of vAPI.window.getWindows()) {
  168. this.unregister(win.document);
  169. }
  170. this.menuItemId = null;
  171. this.menuLabel = null;
  172. this.contexts = null;
  173. this.onCommand = null;
  174. };
  175. })();