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.

381 lines
12 KiB

10 years ago
10 years ago
10 years ago
  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. /* global self */
  17. // For background page
  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. /******************************************************************************/
  31. vAPI.tabs.registerListeners = function() {
  32. if ( typeof this.onNavigation === 'function' ) {
  33. chrome.webNavigation.onCommitted.addListener(this.onNavigation);
  34. }
  35. if ( typeof this.onUpdated === 'function' ) {
  36. chrome.tabs.onUpdated.addListener(this.onUpdated);
  37. }
  38. if ( typeof this.onClosed === 'function' ) {
  39. chrome.tabs.onRemoved.addListener(this.onClosed);
  40. }
  41. if ( typeof this.onPopup === 'function' ) {
  42. chrome.webNavigation.onCreatedNavigationTarget.addListener(this.onPopup);
  43. }
  44. };
  45. /******************************************************************************/
  46. vAPI.tabs.get = function(tabId, callback) {
  47. var onTabReady = function(tab) {
  48. // https://code.google.com/p/chromium/issues/detail?id=410868#c8
  49. if ( chrome.runtime.lastError ) {
  50. return;
  51. }
  52. callback(tab);
  53. }
  54. if ( tabId !== null ) {
  55. chrome.tabs.get(tabId, onTabReady);
  56. return;
  57. }
  58. var onTabReceived = function(tabs) {
  59. // https://code.google.com/p/chromium/issues/detail?id=410868#c8
  60. if ( chrome.runtime.lastError ) {
  61. return;
  62. }
  63. callback(tabs[0]);
  64. };
  65. chrome.tabs.query({ active: true, currentWindow: true }, onTabReceived);
  66. };
  67. /******************************************************************************/
  68. // properties of the details object:
  69. // url: 'URL', // the address that will be opened
  70. // tabId: 1, // the tab is used if set, instead of creating a new one
  71. // index: -1, // undefined: end of the list, -1: following tab, or after index
  72. // active: false, // opens the tab in background - true and undefined: foreground
  73. // select: true // if a tab is already opened with that url, then select it instead of opening a new one
  74. vAPI.tabs.open = function(details) {
  75. var targetURL = details.url;
  76. if ( typeof targetURL !== 'string' || targetURL === '' ) {
  77. return null;
  78. }
  79. // extension pages
  80. if ( /^[\w-]{2,}:/.test(targetURL) !== true ) {
  81. targetURL = vAPI.getURL(targetURL);
  82. }
  83. // dealing with Chrome's asynchronous API
  84. var wrapper = function() {
  85. if ( details.active === undefined ) {
  86. details.active = true;
  87. }
  88. var subWrapper = function() {
  89. var _details = {
  90. url: targetURL,
  91. active: !!details.active
  92. };
  93. if ( details.tabId ) {
  94. // update doesn't accept index, must use move
  95. chrome.tabs.update(details.tabId, _details, function(tab) {
  96. // if the tab doesn't exist
  97. if ( vAPI.lastError() ) {
  98. chrome.tabs.create(_details);
  99. } else if ( details.index !== undefined ) {
  100. chrome.tabs.move(tab.id, {index: details.index});
  101. }
  102. });
  103. } else {
  104. if ( details.index !== undefined ) {
  105. _details.index = details.index;
  106. }
  107. chrome.tabs.create(_details);
  108. }
  109. };
  110. if ( details.index === -1 ) {
  111. vAPI.tabs.get(null, function(tab) {
  112. if ( tab ) {
  113. details.index = tab.index + 1;
  114. } else {
  115. delete details.index;
  116. }
  117. subWrapper();
  118. });
  119. }
  120. else {
  121. subWrapper();
  122. }
  123. };
  124. if ( details.select ) {
  125. chrome.tabs.query({ currentWindow: true }, function(tabs) {
  126. var url = targetURL.replace(rgxHash, '');
  127. // this is questionable
  128. var rgxHash = /#.*/;
  129. var selected = tabs.some(function(tab) {
  130. if ( tab.url.replace(rgxHash, '') === url ) {
  131. chrome.tabs.update(tab.id, { active: true });
  132. return true;
  133. }
  134. });
  135. if ( !selected ) {
  136. wrapper();
  137. }
  138. });
  139. }
  140. else {
  141. wrapper();
  142. }
  143. };
  144. /******************************************************************************/
  145. vAPI.tabs.remove = function(tabId) {
  146. var onTabRemoved = function() {
  147. if ( vAPI.lastError() ) {
  148. }
  149. };
  150. chrome.tabs.remove(tabId, onTabRemoved);
  151. };
  152. /******************************************************************************/
  153. vAPI.tabs.injectScript = function(tabId, details, callback) {
  154. var onScriptExecuted = function() {
  155. // https://code.google.com/p/chromium/issues/detail?id=410868#c8
  156. if ( chrome.runtime.lastError ) {
  157. }
  158. if ( typeof callback === 'function' ) {
  159. callback();
  160. }
  161. };
  162. if ( tabId ) {
  163. chrome.tabs.executeScript(tabId, details, onScriptExecuted);
  164. } else {
  165. chrome.tabs.executeScript(details, onScriptExecuted);
  166. }
  167. };
  168. /******************************************************************************/
  169. // Must read: https://code.google.com/p/chromium/issues/detail?id=410868#c8
  170. // https://github.com/gorhill/uBlock/issues/19
  171. // https://github.com/gorhill/uBlock/issues/207
  172. // Since we may be called asynchronously, the tab id may not exist
  173. // anymore, so this ensures it does still exist.
  174. vAPI.setIcon = function(tabId, img, badge) {
  175. var onIconReady = function() {
  176. if ( vAPI.lastError() ) {
  177. return;
  178. }
  179. chrome.browserAction.setBadgeText({ tabId: tabId, text: badge });
  180. if ( badge !== '' ) {
  181. chrome.browserAction.setBadgeBackgroundColor({ tabId: tabId, color: '#666' });
  182. }
  183. };
  184. chrome.browserAction.setIcon({ tabId: tabId, path: img }, onIconReady);
  185. };
  186. /******************************************************************************/
  187. vAPI.messaging = {
  188. ports: {},
  189. listeners: {},
  190. defaultHandler: null,
  191. NOOPFUNC: function(){},
  192. UNHANDLED: 'vAPI.messaging.notHandled'
  193. };
  194. /******************************************************************************/
  195. vAPI.messaging.listen = function(listenerName, callback) {
  196. this.listeners[listenerName] = callback;
  197. };
  198. /******************************************************************************/
  199. vAPI.messaging.onPortMessage = function(request, port) {
  200. var callback = vAPI.messaging.NOOPFUNC;
  201. if ( request.requestId !== undefined ) {
  202. callback = function(response) {
  203. port.postMessage({
  204. requestId: request.requestId,
  205. portName: request.portName,
  206. msg: response !== undefined ? response : null
  207. });
  208. };
  209. }
  210. // Specific handler
  211. var r = vAPI.messaging.UNHANDLED;
  212. var listener = vAPI.messaging.listeners[request.portName];
  213. if ( typeof listener === 'function' ) {
  214. r = listener(request.msg, port.sender, callback);
  215. }
  216. if ( r !== vAPI.messaging.UNHANDLED ) {
  217. return;
  218. }
  219. // Default handler
  220. r = vAPI.messaging.defaultHandler(request.msg, port.sender, callback);
  221. if ( r !== vAPI.messaging.UNHANDLED ) {
  222. return;
  223. }
  224. console.error('µBlock> messaging > unknown request: %o', request);
  225. // Unhandled:
  226. // Need to callback anyways in case caller expected an answer, or
  227. // else there is a memory leak on caller's side
  228. callback();
  229. };
  230. /******************************************************************************/
  231. vAPI.messaging.onPortDisconnect = function(port) {
  232. port.onDisconnect.removeListener(vAPI.messaging.onPortDisconnect);
  233. port.onMessage.removeListener(vAPI.messaging.onPortMessage);
  234. delete vAPI.messaging.ports[port.name];
  235. };
  236. /******************************************************************************/
  237. vAPI.messaging.onPortConnect = function(port) {
  238. port.onDisconnect.addListener(vAPI.messaging.onPortDisconnect);
  239. port.onMessage.addListener(vAPI.messaging.onPortMessage);
  240. vAPI.messaging.ports[port.name] = port;
  241. };
  242. /******************************************************************************/
  243. vAPI.messaging.setup = function(defaultHandler) {
  244. // Already setup?
  245. if ( this.defaultHandler !== null ) {
  246. return;
  247. }
  248. if ( typeof defaultHandler !== 'function' ) {
  249. defaultHandler = function(){ return vAPI.messaging.UNHANDLED; };
  250. }
  251. this.defaultHandler = defaultHandler;
  252. chrome.runtime.onConnect.addListener(this.onPortConnect);
  253. };
  254. /******************************************************************************/
  255. vAPI.messaging.broadcast = function(message) {
  256. var messageWrapper = {
  257. broadcast: true,
  258. msg: message
  259. };
  260. for ( var portName in this.ports ) {
  261. if ( this.ports.hasOwnProperty(portName) === false ) {
  262. continue;
  263. }
  264. this.ports[portName].postMessage(messageWrapper);
  265. }
  266. };
  267. /******************************************************************************/
  268. vAPI.net = {
  269. registerListeners: function() {
  270. var listeners = [
  271. 'onBeforeRequest',
  272. 'onBeforeSendHeaders',
  273. 'onHeadersReceived'
  274. ];
  275. for (var i = 0; i < listeners.length; ++i) {
  276. chrome.webRequest[listeners[i]].addListener(
  277. this[listeners[i]].callback,
  278. {
  279. 'urls': this[listeners[i]].urls || ['<all_urls>'],
  280. 'types': this[listeners[i]].types || []
  281. },
  282. this[listeners[i]].extra
  283. );
  284. }
  285. }
  286. };
  287. /******************************************************************************/
  288. vAPI.contextMenu = {
  289. create: function(details, callback) {
  290. this.menuId = details.id;
  291. this.callback = callback;
  292. chrome.contextMenus.create(details);
  293. chrome.contextMenus.onClicked.addListener(this.callback);
  294. },
  295. remove: function() {
  296. chrome.contextMenus.onClicked.removeListener(this.callback);
  297. chrome.contextMenus.remove(this.menuId);
  298. }
  299. };
  300. /******************************************************************************/
  301. vAPI.lastError = function() {
  302. return chrome.runtime.lastError;
  303. };
  304. /******************************************************************************/
  305. })();
  306. /******************************************************************************/