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.

225 lines
6.3 KiB

  1. /*******************************************************************************
  2. uMatrix - a browser extension to block requests.
  3. Copyright (C) 2014-2017 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. /* jshint esnext: true */
  17. /* global addMessageListener, removeMessageListener, sendAsyncMessage */
  18. // For non background pages
  19. 'use strict';
  20. /******************************************************************************/
  21. (function(self) {
  22. /******************************************************************************/
  23. // https://bugs.chromium.org/p/project-zero/issues/detail?id=1225&desc=6#c10
  24. if ( self.vAPI === undefined || self.vAPI.uMatrix !== true ) {
  25. self.vAPI = { uMatrix: true };
  26. }
  27. var vAPI = self.vAPI;
  28. vAPI.firefox = true;
  29. vAPI.sessionId = String.fromCharCode(Date.now() % 25 + 97) +
  30. Math.random().toString(36).slice(2);
  31. /******************************************************************************/
  32. vAPI.setTimeout = vAPI.setTimeout || function(callback, delay) {
  33. return setTimeout(function() { callback(); }, delay);
  34. };
  35. /******************************************************************************/
  36. vAPI.shutdown = (function() {
  37. var jobs = [];
  38. var add = function(job) {
  39. jobs.push(job);
  40. };
  41. var exec = function() {
  42. //console.debug('Shutting down...');
  43. var job;
  44. while ( (job = jobs.pop()) ) {
  45. job();
  46. }
  47. };
  48. return {
  49. add: add,
  50. exec: exec
  51. };
  52. })();
  53. /******************************************************************************/
  54. vAPI.messaging = {
  55. listeners: new Set(),
  56. pending: new Map(),
  57. requestId: 1,
  58. connected: false,
  59. setup: function() {
  60. this.addListener(this.builtinListener);
  61. if ( this.toggleListenerCallback === null ) {
  62. this.toggleListenerCallback = this.toggleListener.bind(this);
  63. }
  64. window.addEventListener('pagehide', this.toggleListenerCallback, true);
  65. window.addEventListener('pageshow', this.toggleListenerCallback, true);
  66. },
  67. shutdown: function() {
  68. if ( this.toggleListenerCallback !== null ) {
  69. window.removeEventListener('pagehide', this.toggleListenerCallback, true);
  70. window.removeEventListener('pageshow', this.toggleListenerCallback, true);
  71. }
  72. this.removeAllListeners();
  73. //service pending callbacks
  74. var pending = this.pending;
  75. this.pending.clear();
  76. for ( var callback of pending.values() ) {
  77. if ( typeof callback === 'function' ) {
  78. callback(null);
  79. }
  80. }
  81. },
  82. connect: function() {
  83. if ( !this.connected ) {
  84. if ( this.messageListenerCallback === null ) {
  85. this.messageListenerCallback = this.messageListener.bind(this);
  86. }
  87. addMessageListener(this.messageListenerCallback);
  88. this.connected = true;
  89. }
  90. },
  91. disconnect: function() {
  92. if ( this.connected ) {
  93. removeMessageListener();
  94. this.connected = false;
  95. }
  96. },
  97. messageListener: function(msg) {
  98. var details = JSON.parse(msg);
  99. if ( !details ) {
  100. return;
  101. }
  102. if ( details.broadcast ) {
  103. this.sendToListeners(details.msg);
  104. return;
  105. }
  106. if ( details.requestId ) {
  107. var listener = this.pending.get(details.requestId);
  108. if ( listener !== undefined ) {
  109. this.pending.delete(details.requestId);
  110. listener(details.msg);
  111. return;
  112. }
  113. }
  114. },
  115. messageListenerCallback: null,
  116. builtinListener: function(msg) {
  117. if ( typeof msg.cmd === 'string' && msg.cmd === 'injectScript' ) {
  118. var details = msg.details;
  119. if ( !details.allFrames && window !== window.top ) {
  120. return;
  121. }
  122. self.injectScript(details.file);
  123. }
  124. },
  125. send: function(channelName, message, callback) {
  126. this.connect()
  127. message = {
  128. channelName: self._sandboxId_ + '|' + channelName,
  129. msg: message
  130. };
  131. if ( callback ) {
  132. message.requestId = this.requestId++;
  133. this.pending.set(message.requestId, callback);
  134. }
  135. sendAsyncMessage('umatrix:background', message);
  136. },
  137. toggleListener: function({type, persisted}) {
  138. if ( type === 'pagehide' && !persisted ) {
  139. vAPI.shutdown.exec();
  140. this.shutdown();
  141. return;
  142. }
  143. if ( type === 'pagehide' ) {
  144. this.disconnect();
  145. } else /* if ( type === 'pageshow' ) */ {
  146. this.connect();
  147. }
  148. },
  149. toggleListenerCallback: null,
  150. sendToListeners: function(msg) {
  151. for ( var listener of this.listeners ) {
  152. listener(msg);
  153. }
  154. },
  155. addListener: function(listener) {
  156. this.listeners.add(listener);
  157. this.connect()
  158. },
  159. removeListener: function(listener) {
  160. this.listeners.delete(listener);
  161. },
  162. removeAllListeners: function() {
  163. this.disconnect();
  164. this.listeners.clear();;
  165. }
  166. };
  167. vAPI.messaging.setup()
  168. /******************************************************************************/
  169. // No need to have vAPI client linger around after shutdown if
  170. // we are not a top window (because element picker can still
  171. // be injected in top window).
  172. if ( window !== window.top ) {
  173. vAPI.shutdown.add(function() {
  174. vAPI = null;
  175. });
  176. }
  177. /******************************************************************************/
  178. })(this);
  179. /******************************************************************************/