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.

850 lines
26 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. /*******************************************************************************
  2. µMatrix - a browser extension to block requests.
  3. Copyright (C) 2014 The uBlock 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, µMatrix */
  17. // For background page
  18. /******************************************************************************/
  19. (function() {
  20. 'use strict';
  21. /******************************************************************************/
  22. var vAPI = self.vAPI = self.vAPI || {};
  23. var chrome = self.chrome;
  24. var manifest = chrome.runtime.getManifest();
  25. vAPI.chrome = true;
  26. var noopFunc = function(){};
  27. /******************************************************************************/
  28. vAPI.app = {
  29. name: manifest.name,
  30. version: manifest.version
  31. };
  32. /******************************************************************************/
  33. vAPI.app.start = function() {
  34. // rhill 2013-12-07:
  35. // Relinquish control over javascript execution to the user.
  36. // https://github.com/gorhill/httpswitchboard/issues/74
  37. //chrome.contentSettings.javascript.clear({});
  38. };
  39. /******************************************************************************/
  40. vAPI.app.stop = function() {
  41. chrome.contentSettings.javascript.clear({});
  42. // rhill 2013-12-07:
  43. // Tell Chromium to allow all javascript: µMatrix will control whether
  44. // javascript execute through `Content-Policy-Directive` and webRequest.
  45. // https://github.com/gorhill/httpswitchboard/issues/74
  46. //chrome.contentSettings.javascript.set({
  47. // primaryPattern: 'https://*/*',
  48. // setting: 'allow'
  49. //});
  50. //chrome.contentSettings.javascript.set({
  51. // primaryPattern: 'http://*/*',
  52. // setting: 'allow'
  53. //});
  54. };
  55. /******************************************************************************/
  56. vAPI.app.restart = function() {
  57. chrome.runtime.reload();
  58. };
  59. /******************************************************************************/
  60. // chrome.storage.local.get(null, function(bin){ console.debug('%o', bin); });
  61. vAPI.storage = chrome.storage.local;
  62. /******************************************************************************/
  63. vAPI.tabs = {};
  64. /******************************************************************************/
  65. vAPI.isBehindTheSceneTabId = function(tabId) {
  66. return tabId.toString() === '-1';
  67. };
  68. vAPI.noTabId = '-1';
  69. /******************************************************************************/
  70. vAPI.tabs.registerListeners = function() {
  71. var onNavigationClient = this.onNavigation || noopFunc;
  72. var onUpdatedClient = this.onUpdated || noopFunc;
  73. var onClosedClient = this.onClosed || noopFunc;
  74. // https://developer.chrome.com/extensions/webNavigation
  75. // [onCreatedNavigationTarget ->]
  76. // onBeforeNavigate ->
  77. // onCommitted ->
  78. // onDOMContentLoaded ->
  79. // onCompleted
  80. // The chrome.webRequest.onBeforeRequest() won't be called for everything
  81. // else than `http`/`https`. Thus, in such case, we will bind the tab as
  82. // early as possible in order to increase the likelihood of a context
  83. // properly setup if network requests are fired from within the tab.
  84. // Example: Chromium + case #6 at
  85. // http://raymondhill.net/ublock/popup.html
  86. var reGoodForWebRequestAPI = /^https?:\/\//;
  87. var onCreatedNavigationTarget = function(details) {
  88. //console.debug('onCreatedNavigationTarget: tab id %d = "%s"', details.tabId, details.url);
  89. if ( reGoodForWebRequestAPI.test(details.url) ) {
  90. return;
  91. }
  92. details.tabId = details.tabId.toString();
  93. details.frameId = 0;
  94. onNavigationClient(details);
  95. };
  96. var onUpdated = function(tabId, changeInfo, tab) {
  97. tabId = tabId.toString();
  98. onUpdatedClient(tabId, changeInfo, tab);
  99. };
  100. var onCommitted = function(details) {
  101. if ( details.frameId !== 0 ) {
  102. return;
  103. }
  104. details.tabId = details.tabId.toString();
  105. onNavigationClient(details);
  106. //console.debug('onCommitted: tab id %d = "%s"', details.tabId, details.url);
  107. };
  108. var onClosed = function(tabId) {
  109. onClosedClient(tabId.toString());
  110. };
  111. chrome.webNavigation.onCreatedNavigationTarget.addListener(onCreatedNavigationTarget);
  112. chrome.webNavigation.onCommitted.addListener(onCommitted);
  113. chrome.tabs.onUpdated.addListener(onUpdated);
  114. chrome.tabs.onRemoved.addListener(onClosed);
  115. };
  116. /******************************************************************************/
  117. // tabId: null, // active tab
  118. vAPI.tabs.get = function(tabId, callback) {
  119. var onTabReady = function(tab) {
  120. // https://code.google.com/p/chromium/issues/detail?id=410868#c8
  121. if ( chrome.runtime.lastError ) {
  122. }
  123. if ( tab instanceof Object ) {
  124. tab.id = tab.id.toString();
  125. }
  126. callback(tab);
  127. };
  128. if ( tabId !== null ) {
  129. if ( typeof tabId === 'string' ) {
  130. tabId = parseInt(tabId, 10);
  131. }
  132. chrome.tabs.get(tabId, onTabReady);
  133. return;
  134. }
  135. var onTabReceived = function(tabs) {
  136. // https://code.google.com/p/chromium/issues/detail?id=410868#c8
  137. if ( chrome.runtime.lastError ) {
  138. }
  139. var tab = null;
  140. if ( Array.isArray(tabs) && tabs.length !== 0 ) {
  141. tab = tabs[0];
  142. tab.id = tab.id.toString();
  143. }
  144. callback(tab);
  145. };
  146. chrome.tabs.query({ active: true, currentWindow: true }, onTabReceived);
  147. };
  148. /******************************************************************************/
  149. vAPI.tabs.getAll = function(callback) {
  150. var onTabsReady = function(tabs) {
  151. if ( Array.isArray(tabs) ) {
  152. var i = tabs.length;
  153. while ( i-- ) {
  154. tabs[i].id = tabs[i].id.toString();
  155. }
  156. }
  157. callback(tabs);
  158. };
  159. chrome.tabs.query({ url: '<all_urls>' }, onTabsReady);
  160. };
  161. /******************************************************************************/
  162. // properties of the details object:
  163. // url: 'URL', // the address that will be opened
  164. // tabId: 1, // the tab is used if set, instead of creating a new one
  165. // index: -1, // undefined: end of the list, -1: following tab, or after index
  166. // active: false, // opens the tab in background - true and undefined: foreground
  167. // select: true // if a tab is already opened with that url, then select it instead of opening a new one
  168. vAPI.tabs.open = function(details) {
  169. var targetURL = details.url;
  170. if ( typeof targetURL !== 'string' || targetURL === '' ) {
  171. return null;
  172. }
  173. // extension pages
  174. if ( /^[\w-]{2,}:/.test(targetURL) !== true ) {
  175. targetURL = vAPI.getURL(targetURL);
  176. }
  177. // dealing with Chrome's asynchronous API
  178. var wrapper = function() {
  179. if ( details.active === undefined ) {
  180. details.active = true;
  181. }
  182. var subWrapper = function() {
  183. var _details = {
  184. url: targetURL,
  185. active: !!details.active
  186. };
  187. // Opening a tab from incognito window won't focus the window
  188. // in which the tab was opened
  189. var focusWindow = function(tab) {
  190. if ( tab.active ) {
  191. chrome.windows.update(tab.windowId, { focused: true });
  192. }
  193. };
  194. if ( !details.tabId ) {
  195. if ( details.index !== undefined ) {
  196. _details.index = details.index;
  197. }
  198. chrome.tabs.create(_details, focusWindow);
  199. return;
  200. }
  201. // update doesn't accept index, must use move
  202. chrome.tabs.update(parseInt(details.tabId, 10), _details, function(tab) {
  203. // if the tab doesn't exist
  204. if ( vAPI.lastError() ) {
  205. chrome.tabs.create(_details, focusWindow);
  206. } else if ( details.index !== undefined ) {
  207. chrome.tabs.move(tab.id, {index: details.index});
  208. }
  209. });
  210. };
  211. if ( details.index !== -1 ) {
  212. subWrapper();
  213. return;
  214. }
  215. vAPI.tabs.get(null, function(tab) {
  216. if ( tab ) {
  217. details.index = tab.index + 1;
  218. } else {
  219. delete details.index;
  220. }
  221. subWrapper();
  222. });
  223. };
  224. if ( !details.select ) {
  225. wrapper();
  226. return;
  227. }
  228. chrome.tabs.query({ url: targetURL }, function(tabs) {
  229. var tab = tabs[0];
  230. if ( tab ) {
  231. chrome.tabs.update(tab.id, { active: true }, function(tab) {
  232. chrome.windows.update(tab.windowId, { focused: true });
  233. });
  234. } else {
  235. wrapper();
  236. }
  237. });
  238. };
  239. /******************************************************************************/
  240. // Replace the URL of a tab. Noop if the tab does not exist.
  241. vAPI.tabs.replace = function(tabId, url) {
  242. var targetURL = url;
  243. // extension pages
  244. if ( /^[\w-]{2,}:/.test(targetURL) !== true ) {
  245. targetURL = vAPI.getURL(targetURL);
  246. }
  247. if ( typeof tabId !== 'number' ) {
  248. tabId = parseInt(tabId, 10);
  249. if ( isNaN(tabId) ) {
  250. return;
  251. }
  252. }
  253. chrome.tabs.update(tabId, { url: targetURL }, function() {
  254. // this prevent console error
  255. if ( chrome.runtime.lastError ) {
  256. return;
  257. }
  258. });
  259. };
  260. /******************************************************************************/
  261. vAPI.tabs.remove = function(tabId) {
  262. var onTabRemoved = function() {
  263. if ( vAPI.lastError() ) {
  264. }
  265. };
  266. chrome.tabs.remove(parseInt(tabId, 10), onTabRemoved);
  267. };
  268. /******************************************************************************/
  269. vAPI.tabs.reload = function(tabId /*, flags*/) {
  270. if ( typeof tabId === 'string' ) {
  271. tabId = parseInt(tabId, 10);
  272. }
  273. if ( isNaN(tabId) ) {
  274. return;
  275. }
  276. chrome.tabs.reload(tabId);
  277. };
  278. /******************************************************************************/
  279. vAPI.tabs.injectScript = function(tabId, details, callback) {
  280. var onScriptExecuted = function() {
  281. // https://code.google.com/p/chromium/issues/detail?id=410868#c8
  282. if ( chrome.runtime.lastError ) {
  283. }
  284. if ( typeof callback === 'function' ) {
  285. callback();
  286. }
  287. };
  288. if ( tabId ) {
  289. tabId = parseInt(tabId, 10);
  290. chrome.tabs.executeScript(tabId, details, onScriptExecuted);
  291. } else {
  292. chrome.tabs.executeScript(details, onScriptExecuted);
  293. }
  294. };
  295. /******************************************************************************/
  296. // Must read: https://code.google.com/p/chromium/issues/detail?id=410868#c8
  297. // https://github.com/chrisaljoudi/uBlock/issues/19
  298. // https://github.com/chrisaljoudi/uBlock/issues/207
  299. // Since we may be called asynchronously, the tab id may not exist
  300. // anymore, so this ensures it does still exist.
  301. vAPI.setIcon = function(tabId, iconId, badge) {
  302. tabId = parseInt(tabId, 10);
  303. if ( isNaN(tabId) || tabId <= 0 ) {
  304. return;
  305. }
  306. var onIconReady = function() {
  307. if ( vAPI.lastError() ) {
  308. return;
  309. }
  310. chrome.browserAction.setBadgeText({ tabId: tabId, text: badge });
  311. if ( badge !== '' ) {
  312. chrome.browserAction.setBadgeBackgroundColor({
  313. tabId: tabId,
  314. color: '#000'
  315. });
  316. }
  317. };
  318. var iconSelector = typeof iconId === 'number' ? iconId : 'off';
  319. var iconPaths = {
  320. '19': 'img/browsericons/icon19-' + iconSelector + '.png'/* ,
  321. '38': 'img/browsericons/icon38-' + iconSelector + '.png' */
  322. };
  323. chrome.browserAction.setIcon({ tabId: tabId, path: iconPaths }, onIconReady);
  324. };
  325. /******************************************************************************/
  326. /******************************************************************************/
  327. vAPI.messaging = {
  328. ports: {},
  329. listeners: {},
  330. defaultHandler: null,
  331. NOOPFUNC: noopFunc,
  332. UNHANDLED: 'vAPI.messaging.notHandled'
  333. };
  334. /******************************************************************************/
  335. vAPI.messaging.listen = function(listenerName, callback) {
  336. this.listeners[listenerName] = callback;
  337. };
  338. /******************************************************************************/
  339. vAPI.messaging.onPortMessage = function(request, port) {
  340. var callback = vAPI.messaging.NOOPFUNC;
  341. if ( request.requestId !== undefined ) {
  342. callback = CallbackWrapper.factory(port, request).callback;
  343. }
  344. // Specific handler
  345. var r = vAPI.messaging.UNHANDLED;
  346. var listener = vAPI.messaging.listeners[request.channelName];
  347. if ( typeof listener === 'function' ) {
  348. r = listener(request.msg, port.sender, callback);
  349. }
  350. if ( r !== vAPI.messaging.UNHANDLED ) {
  351. return;
  352. }
  353. // Default handler
  354. r = vAPI.messaging.defaultHandler(request.msg, port.sender, callback);
  355. if ( r !== vAPI.messaging.UNHANDLED ) {
  356. return;
  357. }
  358. console.error('µMatrix> messaging > unknown request: %o', request);
  359. // Unhandled:
  360. // Need to callback anyways in case caller expected an answer, or
  361. // else there is a memory leak on caller's side
  362. callback();
  363. };
  364. /******************************************************************************/
  365. vAPI.messaging.onPortDisconnect = function(port) {
  366. port.onDisconnect.removeListener(vAPI.messaging.onPortDisconnect);
  367. port.onMessage.removeListener(vAPI.messaging.onPortMessage);
  368. delete vAPI.messaging.ports[port.name];
  369. };
  370. /******************************************************************************/
  371. vAPI.messaging.onPortConnect = function(port) {
  372. port.onDisconnect.addListener(vAPI.messaging.onPortDisconnect);
  373. port.onMessage.addListener(vAPI.messaging.onPortMessage);
  374. vAPI.messaging.ports[port.name] = port;
  375. };
  376. /******************************************************************************/
  377. vAPI.messaging.setup = function(defaultHandler) {
  378. // Already setup?
  379. if ( this.defaultHandler !== null ) {
  380. return;
  381. }
  382. if ( typeof defaultHandler !== 'function' ) {
  383. defaultHandler = function(){ return vAPI.messaging.UNHANDLED; };
  384. }
  385. this.defaultHandler = defaultHandler;
  386. chrome.runtime.onConnect.addListener(this.onPortConnect);
  387. };
  388. /******************************************************************************/
  389. vAPI.messaging.broadcast = function(message) {
  390. var messageWrapper = {
  391. broadcast: true,
  392. msg: message
  393. };
  394. for ( var portName in this.ports ) {
  395. if ( this.ports.hasOwnProperty(portName) === false ) {
  396. continue;
  397. }
  398. this.ports[portName].postMessage(messageWrapper);
  399. }
  400. };
  401. /******************************************************************************/
  402. // This allows to avoid creating a closure for every single message which
  403. // expects an answer. Having a closure created each time a message is processed
  404. // has been always bothering me. Another benefit of the implementation here
  405. // is to reuse the callback proxy object, so less memory churning.
  406. //
  407. // https://developers.google.com/speed/articles/optimizing-javascript
  408. // "Creating a closure is significantly slower then creating an inner
  409. // function without a closure, and much slower than reusing a static
  410. // function"
  411. //
  412. // http://hacksoflife.blogspot.ca/2015/01/the-four-horsemen-of-performance.html
  413. // "the dreaded 'uniformly slow code' case where every function takes 1%
  414. // of CPU and you have to make one hundred separate performance optimizations
  415. // to improve performance at all"
  416. //
  417. // http://jsperf.com/closure-no-closure/2
  418. var CallbackWrapper = function(port, request) {
  419. // No need to bind every single time
  420. this.callback = this.proxy.bind(this);
  421. this.messaging = vAPI.messaging;
  422. this.init(port, request);
  423. };
  424. CallbackWrapper.junkyard = [];
  425. CallbackWrapper.factory = function(port, request) {
  426. var wrapper = CallbackWrapper.junkyard.pop();
  427. if ( wrapper ) {
  428. wrapper.init(port, request);
  429. return wrapper;
  430. }
  431. return new CallbackWrapper(port, request);
  432. };
  433. CallbackWrapper.prototype.init = function(port, request) {
  434. this.port = port;
  435. this.request = request;
  436. };
  437. CallbackWrapper.prototype.proxy = function(response) {
  438. // https://github.com/chrisaljoudi/uBlock/issues/383
  439. if ( this.messaging.ports.hasOwnProperty(this.port.name) ) {
  440. this.port.postMessage({
  441. requestId: this.request.requestId,
  442. channelName: this.request.channelName,
  443. msg: response !== undefined ? response : null
  444. });
  445. }
  446. // Mark for reuse
  447. this.port = this.request = null;
  448. CallbackWrapper.junkyard.push(this);
  449. };
  450. /******************************************************************************/
  451. /******************************************************************************/
  452. vAPI.net = {};
  453. /******************************************************************************/
  454. vAPI.net.registerListeners = function() {
  455. var µm = µMatrix;
  456. var µmuri = µm.URI;
  457. var httpRequestHeadersJunkyard = [];
  458. // Abstraction layer to deal with request headers
  459. // >>>>>>>>
  460. var httpRequestHeadersFactory = function(headers) {
  461. var entry = httpRequestHeadersJunkyard.pop();
  462. if ( entry ) {
  463. return entry.init(headers);
  464. }
  465. return new HTTPRequestHeaders(headers);
  466. };
  467. var HTTPRequestHeaders = function(headers) {
  468. this.init(headers);
  469. };
  470. HTTPRequestHeaders.prototype.init = function(headers) {
  471. this.modified = false;
  472. this.headers = headers;
  473. return this;
  474. };
  475. HTTPRequestHeaders.prototype.dispose = function() {
  476. var r = this.modified ? this.headers : null;
  477. this.headers = null;
  478. httpRequestHeadersJunkyard.push(this);
  479. return r;
  480. };
  481. HTTPRequestHeaders.prototype.getHeader = function(target) {
  482. var headers = this.headers;
  483. var header, name;
  484. var i = headers.length;
  485. while ( i-- ) {
  486. header = headers[i];
  487. name = header.name.toLowerCase();
  488. if ( name === target ) {
  489. return header.value;
  490. }
  491. }
  492. return '';
  493. };
  494. HTTPRequestHeaders.prototype.setHeader = function(target, value, create) {
  495. var headers = this.headers;
  496. var header, name;
  497. var i = headers.length;
  498. while ( i-- ) {
  499. header = headers[i];
  500. name = header.name.toLowerCase();
  501. if ( name === target ) {
  502. break;
  503. }
  504. }
  505. if ( i < 0 && !create ) { // Header not found, don't add it
  506. return false;
  507. }
  508. if ( i < 0 ) { // Header not found, add it
  509. headers.push({ name: target, value: value });
  510. } else if ( value === '' ) { // Header found, remove it
  511. headers.splice(i, 1);
  512. } else { // Header found, modify it
  513. header.value = value;
  514. }
  515. this.modified = true;
  516. return true;
  517. };
  518. // <<<<<<<<
  519. // End of: Abstraction layer to deal with request headers
  520. // Normalizing request types
  521. // >>>>>>>>
  522. var normalizeRequestDetails = function(details) {
  523. µmuri.set(details.url);
  524. details.tabId = details.tabId.toString();
  525. details.hostname = µmuri.hostnameFromURI(details.url);
  526. // The rest of the function code is to normalize request type
  527. if ( details.type !== 'other' ) {
  528. return;
  529. }
  530. if ( details.requestHeaders instanceof HTTPRequestHeaders ) {
  531. if ( details.requestHeaders.getHeader('ping-to') !== '' ) {
  532. details.type = 'ping';
  533. return;
  534. }
  535. }
  536. var tail = µmuri.path.slice(-6);
  537. var pos = tail.lastIndexOf('.');
  538. // https://github.com/chrisaljoudi/uBlock/issues/862
  539. // If no transposition possible, transpose to `object` as per
  540. // Chromium bug 410382 (see below)
  541. if ( pos === -1 ) {
  542. return;
  543. }
  544. var ext = tail.slice(pos) + '.';
  545. if ( '.eot.ttf.otf.svg.woff.woff2.'.indexOf(ext) !== -1 ) {
  546. details.type = 'font';
  547. return;
  548. }
  549. // Still need this because often behind-the-scene requests are wrongly
  550. // categorized as 'other'
  551. if ( '.ico.png.gif.jpg.jpeg.webp.'.indexOf(ext) !== -1 ) {
  552. details.type = 'image';
  553. return;
  554. }
  555. };
  556. // <<<<<<<<
  557. // End of: Normalizing request types
  558. // Network event handlers
  559. // >>>>>>>>
  560. var onBeforeRequestClient = this.onBeforeRequest.callback;
  561. var onBeforeRequest = function(details) {
  562. normalizeRequestDetails(details);
  563. return onBeforeRequestClient(details);
  564. };
  565. chrome.webRequest.onBeforeRequest.addListener(
  566. onBeforeRequest,
  567. //function(details) {
  568. // quickProfiler.start('onBeforeRequest');
  569. // var r = onBeforeRequest(details);
  570. // quickProfiler.stop();
  571. // return r;
  572. //},
  573. {
  574. 'urls': this.onBeforeRequest.urls || ['<all_urls>'],
  575. 'types': this.onBeforeRequest.types || []
  576. },
  577. this.onBeforeRequest.extra
  578. );
  579. var onBeforeSendHeadersClient = this.onBeforeSendHeaders.callback;
  580. var onBeforeSendHeaders = function(details) {
  581. details.requestHeaders = httpRequestHeadersFactory(details.requestHeaders);
  582. normalizeRequestDetails(details);
  583. var result = onBeforeSendHeadersClient(details);
  584. if ( typeof result === 'object' ) {
  585. return result;
  586. }
  587. var modifiedHeaders = details.requestHeaders.dispose();
  588. if ( modifiedHeaders !== null ) {
  589. return { requestHeaders: modifiedHeaders };
  590. }
  591. };
  592. chrome.webRequest.onBeforeSendHeaders.addListener(
  593. onBeforeSendHeaders,
  594. {
  595. 'urls': this.onBeforeSendHeaders.urls || ['<all_urls>'],
  596. 'types': this.onBeforeSendHeaders.types || []
  597. },
  598. this.onBeforeSendHeaders.extra
  599. );
  600. var onHeadersReceivedClient = this.onHeadersReceived.callback;
  601. var onHeadersReceived = function(details) {
  602. normalizeRequestDetails(details);
  603. return onHeadersReceivedClient(details);
  604. };
  605. chrome.webRequest.onHeadersReceived.addListener(
  606. onHeadersReceived,
  607. {
  608. 'urls': this.onHeadersReceived.urls || ['<all_urls>'],
  609. 'types': this.onHeadersReceived.types || []
  610. },
  611. this.onHeadersReceived.extra
  612. );
  613. // <<<<<<<<
  614. // End of: Network event handlers
  615. };
  616. /******************************************************************************/
  617. /******************************************************************************/
  618. vAPI.contextMenu = {
  619. create: function(details, callback) {
  620. this.menuId = details.id;
  621. this.callback = callback;
  622. chrome.contextMenus.create(details);
  623. chrome.contextMenus.onClicked.addListener(this.callback);
  624. },
  625. remove: function() {
  626. chrome.contextMenus.onClicked.removeListener(this.callback);
  627. chrome.contextMenus.remove(this.menuId);
  628. }
  629. };
  630. /******************************************************************************/
  631. vAPI.lastError = function() {
  632. return chrome.runtime.lastError;
  633. };
  634. /******************************************************************************/
  635. /******************************************************************************/
  636. // This is called only once, when everything has been loaded in memory after
  637. // the extension was launched. It can be used to inject content scripts
  638. // in already opened web pages, to remove whatever nuisance could make it to
  639. // the web pages before uBlock was ready.
  640. vAPI.onLoadAllCompleted = function() {
  641. };
  642. /******************************************************************************/
  643. /******************************************************************************/
  644. vAPI.punycodeHostname = function(hostname) {
  645. return hostname;
  646. };
  647. vAPI.punycodeURL = function(url) {
  648. return url;
  649. };
  650. /******************************************************************************/
  651. /******************************************************************************/
  652. vAPI.browserData = {};
  653. /******************************************************************************/
  654. // https://developer.chrome.com/extensions/browsingData
  655. vAPI.browserData.clearCache = function(callback) {
  656. chrome.browsingData.removeCache({ since: 0 }, callback);
  657. };
  658. /******************************************************************************/
  659. // Not supported on Chromium
  660. vAPI.browserData.clearOrigin = function(domain, callback) {
  661. // unsupported on Chromium
  662. if ( typeof callback === 'function' ) {
  663. callback(undefined);
  664. }
  665. };
  666. /******************************************************************************/
  667. /******************************************************************************/
  668. // https://developer.chrome.com/extensions/cookies
  669. vAPI.cookies = {};
  670. /******************************************************************************/
  671. vAPI.cookies.start = function() {
  672. var onChanged = function(changeInfo) {
  673. var handler = changeInfo.removed ? this.onRemoved : this.onChanged;
  674. if ( typeof handler !== 'function' ) {
  675. return;
  676. }
  677. handler(changeInfo.cookie);
  678. };
  679. chrome.cookies.onChanged.addListener(onChanged.bind(this));
  680. };
  681. /******************************************************************************/
  682. vAPI.cookies.getAll = function(callback) {
  683. chrome.cookies.getAll({}, callback);
  684. };
  685. /******************************************************************************/
  686. vAPI.cookies.remove = function(details, callback) {
  687. chrome.cookies.remove(details, callback || noopFunc);
  688. };
  689. /******************************************************************************/
  690. /******************************************************************************/
  691. })();
  692. /******************************************************************************/