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.

393 lines
12 KiB

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