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.

385 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. 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 rgxHash = /#.*/;
  127. // this is questionable
  128. var url = targetURL.replace(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. // https://github.com/gorhill/uBlock/issues/383
  204. if ( vAPI.messaging.ports.hasOwnProperty(port.name) === false ) {
  205. return;
  206. }
  207. port.postMessage({
  208. requestId: request.requestId,
  209. portName: request.portName,
  210. msg: response !== undefined ? response : null
  211. });
  212. };
  213. }
  214. // Specific handler
  215. var r = vAPI.messaging.UNHANDLED;
  216. var listener = vAPI.messaging.listeners[request.portName];
  217. if ( typeof listener === 'function' ) {
  218. r = listener(request.msg, port.sender, callback);
  219. }
  220. if ( r !== vAPI.messaging.UNHANDLED ) {
  221. return;
  222. }
  223. // Default handler
  224. r = vAPI.messaging.defaultHandler(request.msg, port.sender, callback);
  225. if ( r !== vAPI.messaging.UNHANDLED ) {
  226. return;
  227. }
  228. console.error('µBlock> messaging > unknown request: %o', request);
  229. // Unhandled:
  230. // Need to callback anyways in case caller expected an answer, or
  231. // else there is a memory leak on caller's side
  232. callback();
  233. };
  234. /******************************************************************************/
  235. vAPI.messaging.onPortDisconnect = function(port) {
  236. port.onDisconnect.removeListener(vAPI.messaging.onPortDisconnect);
  237. port.onMessage.removeListener(vAPI.messaging.onPortMessage);
  238. delete vAPI.messaging.ports[port.name];
  239. };
  240. /******************************************************************************/
  241. vAPI.messaging.onPortConnect = function(port) {
  242. port.onDisconnect.addListener(vAPI.messaging.onPortDisconnect);
  243. port.onMessage.addListener(vAPI.messaging.onPortMessage);
  244. vAPI.messaging.ports[port.name] = port;
  245. };
  246. /******************************************************************************/
  247. vAPI.messaging.setup = function(defaultHandler) {
  248. // Already setup?
  249. if ( this.defaultHandler !== null ) {
  250. return;
  251. }
  252. if ( typeof defaultHandler !== 'function' ) {
  253. defaultHandler = function(){ return vAPI.messaging.UNHANDLED; };
  254. }
  255. this.defaultHandler = defaultHandler;
  256. chrome.runtime.onConnect.addListener(this.onPortConnect);
  257. };
  258. /******************************************************************************/
  259. vAPI.messaging.broadcast = function(message) {
  260. var messageWrapper = {
  261. broadcast: true,
  262. msg: message
  263. };
  264. for ( var portName in this.ports ) {
  265. if ( this.ports.hasOwnProperty(portName) === false ) {
  266. continue;
  267. }
  268. this.ports[portName].postMessage(messageWrapper);
  269. }
  270. };
  271. /******************************************************************************/
  272. vAPI.net = {
  273. registerListeners: function() {
  274. var listeners = [
  275. 'onBeforeRequest',
  276. 'onBeforeSendHeaders',
  277. 'onHeadersReceived'
  278. ];
  279. for (var i = 0; i < listeners.length; ++i) {
  280. chrome.webRequest[listeners[i]].addListener(
  281. this[listeners[i]].callback,
  282. {
  283. 'urls': this[listeners[i]].urls || ['<all_urls>'],
  284. 'types': this[listeners[i]].types || []
  285. },
  286. this[listeners[i]].extra
  287. );
  288. }
  289. }
  290. };
  291. /******************************************************************************/
  292. vAPI.contextMenu = {
  293. create: function(details, callback) {
  294. this.menuId = details.id;
  295. this.callback = callback;
  296. chrome.contextMenus.create(details);
  297. chrome.contextMenus.onClicked.addListener(this.callback);
  298. },
  299. remove: function() {
  300. chrome.contextMenus.onClicked.removeListener(this.callback);
  301. chrome.contextMenus.remove(this.menuId);
  302. }
  303. };
  304. /******************************************************************************/
  305. vAPI.lastError = function() {
  306. return chrome.runtime.lastError;
  307. };
  308. /******************************************************************************/
  309. })();
  310. /******************************************************************************/