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.

454 lines
14 KiB

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