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.

268 lines
8.2 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. /*******************************************************************************
  2. uBlock Origin - a browser extension to block requests.
  3. Copyright (C) 2014-2015 The uBlock Origin authors
  4. Copyright (C) 2014-present Raymond Hill
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 3 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program. If not, see {http://www.gnu.org/licenses/}.
  15. Home: https://github.com/gorhill/uBlock
  16. */
  17. // For non-background page
  18. 'use strict';
  19. /******************************************************************************/
  20. // https://github.com/chrisaljoudi/uBlock/issues/456
  21. // Skip if already injected.
  22. // >>>>>>>> start of HUGE-IF-BLOCK
  23. if (
  24. typeof vAPI === 'object' &&
  25. vAPI.randomToken instanceof Function === false
  26. ) {
  27. /******************************************************************************/
  28. /******************************************************************************/
  29. vAPI.randomToken = function() {
  30. const now = Date.now();
  31. return String.fromCharCode(now % 26 + 97) +
  32. Math.floor((1 + Math.random()) * now).toString(36);
  33. };
  34. vAPI.sessionId = vAPI.randomToken();
  35. vAPI.setTimeout = vAPI.setTimeout || self.setTimeout.bind(self);
  36. /******************************************************************************/
  37. vAPI.shutdown = {
  38. jobs: [],
  39. add: function(job) {
  40. this.jobs.push(job);
  41. },
  42. exec: function() {
  43. // Shutdown asynchronously, to ensure shutdown jobs are called from
  44. // the top context.
  45. self.requestIdleCallback(( ) => {
  46. const jobs = this.jobs.slice();
  47. this.jobs.length = 0;
  48. while ( jobs.length !== 0 ) {
  49. (jobs.pop())();
  50. }
  51. });
  52. },
  53. remove: function(job) {
  54. let pos;
  55. while ( (pos = this.jobs.indexOf(job)) !== -1 ) {
  56. this.jobs.splice(pos, 1);
  57. }
  58. }
  59. };
  60. /******************************************************************************/
  61. vAPI.messaging = {
  62. port: null,
  63. portTimer: null,
  64. portTimerDelay: 10000,
  65. extended: undefined,
  66. extensions: [],
  67. msgIdGenerator: 1,
  68. pending: new Map(),
  69. shuttingDown: false,
  70. shutdown: function() {
  71. this.shuttingDown = true;
  72. this.destroyPort();
  73. },
  74. // https://github.com/uBlockOrigin/uBlock-issues/issues/403
  75. // Spurious disconnection can happen, so do not consider such events
  76. // as world-ending, i.e. stay around. Except for embedded frames.
  77. disconnectListener: function() {
  78. this.port = null;
  79. if ( window !== window.top ) {
  80. vAPI.shutdown.exec();
  81. }
  82. },
  83. disconnectListenerBound: null,
  84. messageListener: function(details) {
  85. if ( details instanceof Object === false ) { return; }
  86. // Response to specific message previously sent
  87. if ( details.msgId !== undefined ) {
  88. const resolver = this.pending.get(details.msgId);
  89. if ( resolver !== undefined ) {
  90. this.pending.delete(details.msgId);
  91. resolver(details.msg);
  92. return;
  93. }
  94. }
  95. // Unhandled messages
  96. this.extensions.every(ext => ext.canProcessMessage(details) !== true);
  97. },
  98. messageListenerBound: null,
  99. canDestroyPort: function() {
  100. return this.pending.size === 0 &&
  101. (
  102. this.extensions.length === 0 ||
  103. this.extensions.every(e => e.canDestroyPort())
  104. );
  105. },
  106. mustDestroyPort: function() {
  107. if ( this.extensions.length === 0 ) { return; }
  108. this.extensions.forEach(e => e.mustDestroyPort());
  109. this.extensions.length = 0;
  110. },
  111. portPoller: function() {
  112. this.portTimer = null;
  113. if ( this.port !== null && this.canDestroyPort() ) {
  114. return this.destroyPort();
  115. }
  116. this.portTimer = vAPI.setTimeout(this.portPollerBound, this.portTimerDelay);
  117. this.portTimerDelay = Math.min(this.portTimerDelay * 2, 60 * 60 * 1000);
  118. },
  119. portPollerBound: null,
  120. destroyPort: function() {
  121. if ( this.portTimer !== null ) {
  122. clearTimeout(this.portTimer);
  123. this.portTimer = null;
  124. }
  125. const port = this.port;
  126. if ( port !== null ) {
  127. port.disconnect();
  128. port.onMessage.removeListener(this.messageListenerBound);
  129. port.onDisconnect.removeListener(this.disconnectListenerBound);
  130. this.port = null;
  131. }
  132. this.mustDestroyPort();
  133. // service pending callbacks
  134. if ( this.pending.size !== 0 ) {
  135. const pending = this.pending;
  136. this.pending = new Map();
  137. for ( const resolver of pending.values() ) {
  138. resolver();
  139. }
  140. }
  141. },
  142. createPort: function() {
  143. if ( this.shuttingDown ) { return null; }
  144. if ( this.messageListenerBound === null ) {
  145. this.messageListenerBound = this.messageListener.bind(this);
  146. this.disconnectListenerBound = this.disconnectListener.bind(this);
  147. this.portPollerBound = this.portPoller.bind(this);
  148. }
  149. try {
  150. this.port = browser.runtime.connect({name: vAPI.sessionId}) || null;
  151. } catch (ex) {
  152. this.port = null;
  153. }
  154. // Not having a valid port at this point means the main process is
  155. // not available: no point keeping the content scripts alive.
  156. if ( this.port === null ) {
  157. vAPI.shutdown.exec();
  158. return null;
  159. }
  160. this.port.onMessage.addListener(this.messageListenerBound);
  161. this.port.onDisconnect.addListener(this.disconnectListenerBound);
  162. this.portTimerDelay = 10000;
  163. if ( this.portTimer === null ) {
  164. this.portTimer = vAPI.setTimeout(
  165. this.portPollerBound,
  166. this.portTimerDelay
  167. );
  168. }
  169. return this.port;
  170. },
  171. getPort: function() {
  172. return this.port !== null ? this.port : this.createPort();
  173. },
  174. send: function(channel, msg) {
  175. // Too large a gap between the last request and the last response means
  176. // the main process is no longer reachable: memory leaks and bad
  177. // performance become a risk -- especially for long-lived, dynamic
  178. // pages. Guard against this.
  179. if ( this.pending.size > 50 ) {
  180. vAPI.shutdown.exec();
  181. }
  182. const port = this.getPort();
  183. if ( port === null ) {
  184. return Promise.resolve();
  185. }
  186. const msgId = this.msgIdGenerator++;
  187. const promise = new Promise(resolve => {
  188. this.pending.set(msgId, resolve);
  189. });
  190. port.postMessage({ channel, msgId, msg });
  191. return promise;
  192. },
  193. // Dynamically extend capabilities.
  194. extend: function() {
  195. if ( this.extended === undefined ) {
  196. this.extended = vAPI.messaging.send('vapi', {
  197. what: 'extendClient'
  198. }).then(( ) => {
  199. return self.vAPI instanceof Object &&
  200. this.extensions.length !== 0;
  201. }).catch(( ) => {
  202. });
  203. }
  204. return this.extended;
  205. },
  206. };
  207. vAPI.shutdown.add(( ) => {
  208. vAPI.messaging.shutdown();
  209. window.vAPI = undefined;
  210. });
  211. /******************************************************************************/
  212. /******************************************************************************/
  213. }
  214. // <<<<<<<< end of HUGE-IF-BLOCK
  215. /*******************************************************************************
  216. DO NOT:
  217. - Remove the following code
  218. - Add code beyond the following code
  219. Reason:
  220. - https://github.com/gorhill/uBlock/pull/3721
  221. - uBO never uses the return value from injected content scripts
  222. **/
  223. void 0;