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.

253 lines
7.5 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. /*******************************************************************************
  2. uMatrix - a browser extension to block requests.
  3. Copyright (C) 2014-2018 The uMatrix/uBlock Origin 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/uMatrix
  15. */
  16. // For non background pages
  17. 'use strict';
  18. /******************************************************************************/
  19. (function(self) {
  20. /******************************************************************************/
  21. // https://bugs.chromium.org/p/project-zero/issues/detail?id=1225&desc=6#c10
  22. if ( self.vAPI === undefined || self.vAPI.uMatrix !== true ) {
  23. self.vAPI = { uMatrix: true };
  24. }
  25. var vAPI = self.vAPI;
  26. var chrome = self.chrome;
  27. // https://github.com/chrisaljoudi/uBlock/issues/456
  28. // Already injected?
  29. if ( vAPI.vapiClientInjected ) {
  30. //console.debug('vapi-client.js already injected: skipping.');
  31. return;
  32. }
  33. vAPI.vapiClientInjected = true;
  34. vAPI.sessionId = String.fromCharCode(Date.now() % 26 + 97) +
  35. Math.random().toString(36).slice(2);
  36. /******************************************************************************/
  37. vAPI.shutdown = (function() {
  38. var jobs = [];
  39. var add = function(job) {
  40. jobs.push(job);
  41. };
  42. var exec = function() {
  43. //console.debug('Shutting down...');
  44. var job;
  45. while ( (job = jobs.pop()) ) {
  46. job();
  47. }
  48. };
  49. return {
  50. add: add,
  51. exec: exec
  52. };
  53. })();
  54. /******************************************************************************/
  55. vAPI.messaging = {
  56. port: null,
  57. portTimer: null,
  58. portTimerDelay: 10000,
  59. listeners: new Set(),
  60. pending: new Map(),
  61. auxProcessId: 1,
  62. shuttingDown: false,
  63. shutdown: function() {
  64. this.shuttingDown = true;
  65. this.destroyPort();
  66. },
  67. disconnectListener: function() {
  68. this.port = null;
  69. vAPI.shutdown.exec();
  70. },
  71. disconnectListenerBound: null,
  72. messageListener: function(details) {
  73. if ( !details ) { return; }
  74. // Sent to all listeners
  75. if ( details.broadcast ) {
  76. this.sendToListeners(details.msg);
  77. return;
  78. }
  79. // Response to specific message previously sent
  80. var listener;
  81. if ( details.auxProcessId ) {
  82. listener = this.pending.get(details.auxProcessId);
  83. if ( listener !== undefined ) {
  84. this.pending.delete(details.auxProcessId);
  85. listener(details.msg);
  86. return;
  87. }
  88. }
  89. },
  90. messageListenerCallback: null,
  91. portPoller: function() {
  92. this.portTimer = null;
  93. if (
  94. this.port !== null &&
  95. this.listeners.size === 0 &&
  96. this.pending.size === 0
  97. ) {
  98. return this.destroyPort();
  99. }
  100. this.portTimer = vAPI.setTimeout(this.portPollerBound, this.portTimerDelay);
  101. this.portTimerDelay = Math.min(this.portTimerDelay * 2, 60 * 60 * 1000);
  102. },
  103. portPollerBound: null,
  104. destroyPort: function() {
  105. if ( this.portTimer !== null ) {
  106. clearTimeout(this.portTimer);
  107. this.portTimer = null;
  108. }
  109. var port = this.port;
  110. if ( port !== null ) {
  111. port.disconnect();
  112. port.onMessage.removeListener(this.messageListenerCallback);
  113. port.onDisconnect.removeListener(this.disconnectListenerBound);
  114. this.port = null;
  115. }
  116. this.listeners.clear();
  117. // service pending callbacks
  118. if ( this.pending.size !== 0 ) {
  119. var pending = this.pending;
  120. this.pending = new Map();
  121. for ( var callback of pending.values() ) {
  122. if ( typeof callback === 'function' ) {
  123. callback(null);
  124. }
  125. }
  126. }
  127. },
  128. createPort: function() {
  129. if ( this.shuttingDown ) { return null; }
  130. if ( this.messageListenerCallback === null ) {
  131. this.messageListenerCallback = this.messageListener.bind(this);
  132. this.disconnectListenerBound = this.disconnectListener.bind(this);
  133. this.portPollerBound = this.portPoller.bind(this);
  134. }
  135. try {
  136. this.port = chrome.runtime.connect({name: vAPI.sessionId}) || null;
  137. } catch (ex) {
  138. this.port = null;
  139. }
  140. if ( this.port !== null ) {
  141. this.port.onMessage.addListener(this.messageListenerCallback);
  142. this.port.onDisconnect.addListener(this.disconnectListenerBound);
  143. this.portTimerDelay = 10000;
  144. if ( this.portTimer === null ) {
  145. this.portTimer = vAPI.setTimeout(
  146. this.portPollerBound,
  147. this.portTimerDelay
  148. );
  149. }
  150. }
  151. return this.port;
  152. },
  153. getPort: function() {
  154. return this.port !== null ? this.port : this.createPort();
  155. },
  156. send: function(channelName, message, callback) {
  157. // Too large a gap between the last request and the last response means
  158. // the main process is no longer reachable: memory leaks and bad
  159. // performance become a risk -- especially for long-lived, dynamic
  160. // pages. Guard against this.
  161. if ( this.pending.size > 25 ) {
  162. vAPI.shutdown.exec();
  163. }
  164. var port = this.getPort();
  165. if ( port === null ) {
  166. if ( typeof callback === 'function' ) { callback(); }
  167. return;
  168. }
  169. var auxProcessId;
  170. if ( callback ) {
  171. auxProcessId = this.auxProcessId++;
  172. this.pending.set(auxProcessId, callback);
  173. }
  174. port.postMessage({
  175. channelName: channelName,
  176. auxProcessId: auxProcessId,
  177. msg: message
  178. });
  179. },
  180. addListener: function(listener) {
  181. this.listeners.add(listener);
  182. this.getPort();
  183. },
  184. removeListener: function(listener) {
  185. this.listeners.delete(listener);
  186. },
  187. removeAllListeners: function() {
  188. this.listeners.clear();
  189. },
  190. sendToListeners: function(msg) {
  191. for ( var listener of this.listeners ) {
  192. listener(msg);
  193. }
  194. }
  195. };
  196. /******************************************************************************/
  197. // No need to have vAPI client linger around after shutdown if
  198. // we are not a top window (because element picker can still
  199. // be injected in top window).
  200. if ( window !== window.top ) {
  201. vAPI.shutdown.add(function() {
  202. vAPI = null;
  203. });
  204. }
  205. /******************************************************************************/
  206. vAPI.setTimeout = vAPI.setTimeout || function(callback, delay) {
  207. setTimeout(function() { callback(); }, delay);
  208. };
  209. /******************************************************************************/
  210. })(this); // jshint ignore: line
  211. /******************************************************************************/