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.

308 lines
9.0 KiB

  1. /*******************************************************************************
  2. uBlock Origin - a browser extension to block requests.
  3. Copyright (C) 2019-present Raymond Hill
  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. // For non-background page
  17. 'use strict';
  18. /******************************************************************************/
  19. // Direct messaging connection ability
  20. (( ) => {
  21. // >>>>>>>> start of private namespace
  22. if (
  23. typeof vAPI !== 'object' ||
  24. vAPI.messaging instanceof Object === false ||
  25. vAPI.MessagingConnection instanceof Function
  26. ) {
  27. return;
  28. }
  29. const listeners = new Set();
  30. const connections = new Map();
  31. vAPI.MessagingConnection = class {
  32. constructor(handler, details) {
  33. this.messaging = vAPI.messaging;
  34. this.handler = handler;
  35. this.id = details.id;
  36. this.to = details.to;
  37. this.toToken = details.toToken;
  38. this.from = details.from;
  39. this.fromToken = details.fromToken;
  40. this.checkTimer = undefined;
  41. // On Firefox it appears ports are not automatically disconnected
  42. // when navigating to another page.
  43. const ctor = vAPI.MessagingConnection;
  44. if ( ctor.pagehide !== undefined ) { return; }
  45. ctor.pagehide = ( ) => {
  46. for ( const connection of connections.values() ) {
  47. connection.disconnect();
  48. connection.handler(
  49. connection.toDetails('connectionBroken')
  50. );
  51. }
  52. };
  53. window.addEventListener('pagehide', ctor.pagehide);
  54. }
  55. toDetails(what, payload) {
  56. return {
  57. what: what,
  58. id: this.id,
  59. from: this.from,
  60. fromToken: this.fromToken,
  61. to: this.to,
  62. toToken: this.toToken,
  63. payload: payload
  64. };
  65. }
  66. disconnect() {
  67. if ( this.checkTimer !== undefined ) {
  68. clearTimeout(this.checkTimer);
  69. this.checkTimer = undefined;
  70. }
  71. connections.delete(this.id);
  72. const port = this.messaging.getPort();
  73. if ( port === null ) { return; }
  74. port.postMessage({
  75. channel: 'vapi',
  76. msg: this.toDetails('connectionBroken'),
  77. });
  78. }
  79. checkAsync() {
  80. if ( this.checkTimer !== undefined ) {
  81. clearTimeout(this.checkTimer);
  82. }
  83. this.checkTimer = vAPI.setTimeout(
  84. ( ) => { this.check(); },
  85. 499
  86. );
  87. }
  88. check() {
  89. this.checkTimer = undefined;
  90. if ( connections.has(this.id) === false ) { return; }
  91. const port = this.messaging.getPort();
  92. if ( port === null ) { return; }
  93. port.postMessage({
  94. channel: 'vapi',
  95. msg: this.toDetails('connectionCheck'),
  96. });
  97. this.checkAsync();
  98. }
  99. receive(details) {
  100. switch ( details.what ) {
  101. case 'connectionAccepted':
  102. this.toToken = details.toToken;
  103. this.handler(details);
  104. this.checkAsync();
  105. break;
  106. case 'connectionBroken':
  107. connections.delete(this.id);
  108. this.handler(details);
  109. break;
  110. case 'connectionMessage':
  111. this.handler(details);
  112. this.checkAsync();
  113. break;
  114. case 'connectionCheck':
  115. const port = this.messaging.getPort();
  116. if ( port === null ) { return; }
  117. if ( connections.has(this.id) ) {
  118. this.checkAsync();
  119. } else {
  120. details.what = 'connectionBroken';
  121. port.postMessage({ channel: 'vapi', msg: details });
  122. }
  123. break;
  124. case 'connectionRefused':
  125. connections.delete(this.id);
  126. this.handler(details);
  127. break;
  128. }
  129. }
  130. send(payload) {
  131. const port = this.messaging.getPort();
  132. if ( port === null ) { return; }
  133. port.postMessage({
  134. channel: 'vapi',
  135. msg: this.toDetails('connectionMessage', payload),
  136. });
  137. }
  138. static addListener(listener) {
  139. listeners.add(listener);
  140. }
  141. static async connectTo(from, to, handler) {
  142. const port = vAPI.messaging.getPort();
  143. if ( port === null ) { return; }
  144. const connection = new vAPI.MessagingConnection(handler, {
  145. id: `${from}-${to}-${vAPI.sessionId}`,
  146. to: to,
  147. from: from,
  148. fromToken: port.name
  149. });
  150. connections.set(connection.id, connection);
  151. port.postMessage({
  152. channel: 'vapi',
  153. msg: {
  154. what: 'connectionRequested',
  155. id: connection.id,
  156. from: from,
  157. fromToken: port.name,
  158. to: to,
  159. }
  160. });
  161. return connection.id;
  162. }
  163. static disconnectFrom(connectionId) {
  164. const connection = connections.get(connectionId);
  165. if ( connection === undefined ) { return; }
  166. connection.disconnect();
  167. }
  168. static sendTo(connectionId, payload) {
  169. const connection = connections.get(connectionId);
  170. if ( connection === undefined ) { return; }
  171. connection.send(payload);
  172. }
  173. static canDestroyPort() {
  174. return listeners.length === 0 && connections.size === 0;
  175. }
  176. static mustDestroyPort() {
  177. if ( connections.size === 0 ) { return; }
  178. for ( const connection of connections.values() ) {
  179. connection.receive({ what: 'connectionBroken' });
  180. }
  181. connections.clear();
  182. }
  183. static canProcessMessage(details) {
  184. if ( details.channel !== 'vapi' ) { return; }
  185. switch ( details.msg.what ) {
  186. case 'connectionAccepted':
  187. case 'connectionBroken':
  188. case 'connectionCheck':
  189. case 'connectionMessage':
  190. case 'connectionRefused': {
  191. const connection = connections.get(details.msg.id);
  192. if ( connection === undefined ) { break; }
  193. connection.receive(details.msg);
  194. return true;
  195. }
  196. case 'connectionRequested':
  197. if ( listeners.length === 0 ) { return; }
  198. const port = vAPI.messaging.getPort();
  199. if ( port === null ) { break; }
  200. let listener, result;
  201. for ( listener of listeners ) {
  202. result = listener(details.msg);
  203. if ( result !== undefined ) { break; }
  204. }
  205. if ( result === undefined ) { break; }
  206. if ( result === true ) {
  207. details.msg.what = 'connectionAccepted';
  208. details.msg.toToken = port.name;
  209. const connection = new vAPI.MessagingConnection(
  210. listener,
  211. details.msg
  212. );
  213. connections.set(connection.id, connection);
  214. } else {
  215. details.msg.what = 'connectionRefused';
  216. }
  217. port.postMessage(details);
  218. return true;
  219. default:
  220. break;
  221. }
  222. }
  223. };
  224. vAPI.messaging.extensions.push(vAPI.MessagingConnection);
  225. // <<<<<<<< end of private namespace
  226. })();
  227. /******************************************************************************/
  228. // Broadcast listening ability
  229. (( ) => {
  230. // >>>>>>>> start of private namespace
  231. if (
  232. typeof vAPI !== 'object' ||
  233. vAPI.messaging instanceof Object === false ||
  234. vAPI.broadcastListener instanceof Object
  235. ) {
  236. return;
  237. }
  238. const listeners = new Set();
  239. vAPI.broadcastListener = {
  240. add: function(listener) {
  241. listeners.add(listener);
  242. vAPI.messaging.getPort();
  243. },
  244. remove: function(listener) {
  245. listeners.delete(listener);
  246. },
  247. canDestroyPort() {
  248. return listeners.size === 0;
  249. },
  250. mustDestroyPort() {
  251. listeners.clear();
  252. },
  253. canProcessMessage(details) {
  254. if ( details.broadcast === false ) { return; }
  255. for ( const listener of listeners ) {
  256. listener(details.msg);
  257. }
  258. },
  259. };
  260. vAPI.messaging.extensions.push(vAPI.broadcastListener);
  261. // <<<<<<<< end of private namespace
  262. })();
  263. /******************************************************************************/
  264. /*******************************************************************************
  265. DO NOT:
  266. - Remove the following code
  267. - Add code beyond the following code
  268. Reason:
  269. - https://github.com/gorhill/uBlock/pull/3721
  270. - uBO never uses the return value from injected content scripts
  271. **/
  272. void 0;