You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

329 lines
9.9 KiB

  1. /*******************************************************************************
  2. µBlock - a Chromium browser extension to block requests.
  3. Copyright (C) 2014 The µBlock authors
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see {http://www.gnu.org/licenses/}.
  14. Home: https://github.com/gorhill/uBlock
  15. */
  16. // For background page
  17. /* global SafariBrowserTab, Services, XPCOMUtils */
  18. /******************************************************************************/
  19. (function() {
  20. 'use strict';
  21. /******************************************************************************/
  22. self.vAPI = self.vAPI || {};
  23. var vAPI = self.vAPI;
  24. var chrome = self.chrome;
  25. vAPI.chrome = true;
  26. /******************************************************************************/
  27. vAPI.storage = chrome.storage.local;
  28. /******************************************************************************/
  29. vAPI.tabs = {
  30. registerListeners: function() {
  31. if (typeof this.onNavigation === 'function') {
  32. chrome.webNavigation.onCommitted.addListener(this.onNavigation);
  33. }
  34. if (typeof this.onUpdated === 'function') {
  35. chrome.tabs.onUpdated.addListener(this.onUpdated);
  36. }
  37. if (typeof this.onClosed === 'function') {
  38. chrome.tabs.onRemoved.addListener(this.onClosed);
  39. }
  40. if (typeof this.onPopup === 'function') {
  41. chrome.webNavigation.onCreatedNavigationTarget.addListener(this.onPopup);
  42. }
  43. },
  44. get: function(tabId, callback) {
  45. if (tabId === null) {
  46. chrome.tabs.query(
  47. {
  48. active: true,
  49. currentWindow: true
  50. },
  51. function(tabs) {
  52. callback(tabs[0]);
  53. }
  54. );
  55. }
  56. else {
  57. chrome.tabs.get(tabId, callback);
  58. }
  59. },
  60. /*open: function(details) {
  61. // to keep incognito context?
  62. chrome.windows.getCurrent(function(win) {
  63. details.windowId = win.windowId;
  64. chrome.tabs.create(details);
  65. });
  66. },*/
  67. open: function(details) {
  68. if (!details.url) {
  69. return null;
  70. }
  71. // extension pages
  72. else if (!details.url.match(/^\w{2,20}:/)) {
  73. details.url = vAPI.getURL(details.url);
  74. }
  75. // dealing with Chrome's asynhronous API
  76. var wrapper = function() {
  77. if (details.active === undefined) {
  78. details.active = true;
  79. }
  80. var subWrapper = function() {
  81. var _details = {
  82. url: details.url,
  83. active: !!details.active
  84. };
  85. if (details.tabId) {
  86. // update doesn't accept index, must use move
  87. chrome.tabs.update(details.tabId, _details, function(tab) {
  88. // if the tab doesn't exist
  89. if (chrome.runtime.lastError) {
  90. chrome.tabs.create(_details);
  91. }
  92. else if (details.index !== undefined) {
  93. chrome.tabs.move(tab.id, {index: details.index});
  94. }
  95. });
  96. }
  97. else {
  98. if (details.index !== undefined) {
  99. _details.index = details.index;
  100. }
  101. chrome.tabs.create(_details);
  102. }
  103. };
  104. if (details.index === -1) {
  105. vAPI.tabs.get(null, function(tab) {
  106. if (tab) {
  107. details.index = tab.index + 1;
  108. }
  109. else {
  110. delete details.index;
  111. }
  112. subWrapper();
  113. });
  114. }
  115. else {
  116. subWrapper();
  117. }
  118. };
  119. if (details.select) {
  120. // note that currentWindow may be even the window of Developer Tools
  121. // so, test with setTimeout...
  122. chrome.tabs.query({currentWindow: true}, function(tabs) {
  123. var url = details.url.replace(rgxHash, '');
  124. // this is questionable
  125. var rgxHash = /#.*/;
  126. tabs = tabs.some(function(tab) {
  127. if (tab.url.replace(rgxHash, '') === url) {
  128. chrome.tabs.update(tab.id, {active: true});
  129. return true;
  130. }
  131. });
  132. if (!tabs) {
  133. wrapper();
  134. }
  135. });
  136. }
  137. else {
  138. wrapper();
  139. }
  140. },
  141. close: chrome.tabs.remove.bind(chrome.tabs),
  142. injectScript: function(tabId, details, callback) {
  143. if (!callback) {
  144. callback = function(){};
  145. }
  146. if (tabId) {
  147. chrome.tabs.executeScript(tabId, details, callback);
  148. }
  149. else {
  150. chrome.tabs.executeScript(details, callback);
  151. }
  152. }
  153. };
  154. /******************************************************************************/
  155. // Must read: https://code.google.com/p/chromium/issues/detail?id=410868#c8
  156. // https://github.com/gorhill/uBlock/issues/19
  157. // https://github.com/gorhill/uBlock/issues/207
  158. // Since we may be called asynchronously, the tab id may not exist
  159. // anymore, so this ensures it does still exist.
  160. vAPI.setIcon = function(tabId, img, badge) {
  161. var onIconReady = function() {
  162. if ( chrome.runtime.lastError ) {
  163. return;
  164. }
  165. chrome.browserAction.setBadgeText({ tabId: tabId, text: badge });
  166. if ( badge !== '' ) {
  167. chrome.browserAction.setBadgeBackgroundColor({ tabId: tabId, color: '#666' });
  168. }
  169. };
  170. chrome.browserAction.setIcon({ tabId: tabId, path: img }, onIconReady);
  171. };
  172. /******************************************************************************/
  173. vAPI.messaging = {
  174. ports: {},
  175. listeners: {},
  176. listen: function(listenerName, callback) {
  177. this.listeners[listenerName] = callback;
  178. },
  179. setup: function(connector) {
  180. if ( this.connector ) {
  181. return;
  182. }
  183. this.connector = function(port) {
  184. var onMessage = function(request) {
  185. var callback = function(response) {
  186. if (chrome.runtime.lastError || response === undefined) {
  187. return;
  188. }
  189. if (request.requestId) {
  190. port.postMessage({
  191. requestId: request.requestId,
  192. portName: request.portName,
  193. msg: response
  194. });
  195. }
  196. };
  197. var listener = connector(request.msg, port.sender, callback);
  198. if ( listener === null ) {
  199. listener = vAPI.messaging.listeners[request.portName];
  200. if (typeof listener === 'function') {
  201. listener(request.msg, port.sender, callback);
  202. } else {
  203. console.error('µBlock> messaging > unknown request: %o', request);
  204. }
  205. }
  206. };
  207. var onDisconnect = function(port) {
  208. port.onDisconnect.removeListener(onDisconnect);
  209. port.onMessage.removeListener(onMessage);
  210. delete vAPI.messaging.ports[port.name];
  211. };
  212. port.onDisconnect.addListener(onDisconnect);
  213. port.onMessage.addListener(onMessage);
  214. vAPI.messaging.ports[port.name] = port;
  215. };
  216. chrome.runtime.onConnect.addListener(this.connector);
  217. },
  218. broadcast: function(message) {
  219. message = {
  220. broadcast: true,
  221. msg: message
  222. };
  223. for ( var portName in this.ports ) {
  224. this.ports[portName].postMessage(message);
  225. }
  226. }
  227. };
  228. /******************************************************************************/
  229. vAPI.net = {
  230. registerListeners: function() {
  231. var listeners = [
  232. 'onBeforeRequest',
  233. 'onBeforeSendHeaders',
  234. 'onHeadersReceived'
  235. ];
  236. for (var i = 0; i < listeners.length; ++i) {
  237. chrome.webRequest[listeners[i]].addListener(
  238. this[listeners[i]].callback,
  239. {
  240. 'urls': this[listeners[i]].urls || ['<all_urls>'],
  241. 'types': this[listeners[i]].types || []
  242. },
  243. this[listeners[i]].extra
  244. );
  245. }
  246. }
  247. };
  248. /******************************************************************************/
  249. vAPI.contextMenu = {
  250. create: function(details, callback) {
  251. this.menuId = details.id;
  252. this.callback = callback;
  253. chrome.contextMenus.create(details);
  254. chrome.contextMenus.onClicked.addListener(this.callback);
  255. },
  256. remove: function() {
  257. chrome.contextMenus.onClicked.removeListener(this.callback);
  258. chrome.contextMenus.remove(this.menuId);
  259. }
  260. };
  261. /******************************************************************************/
  262. vAPI.lastError = function() {
  263. return chrome.runtime.lastError;
  264. };
  265. /******************************************************************************/
  266. })();
  267. /******************************************************************************/