ipc-dispatch.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
  2. import { MessagePortMain } from '@electron/internal/browser/message-port-main';
  3. import type { ServiceWorkerMain } from 'electron/main';
  4. import { ipcMain } from 'electron/main';
  5. const v8Util = process._linkedBinding('electron_common_v8_util');
  6. const webFrameMainBinding = process._linkedBinding('electron_browser_web_frame_main');
  7. const addReplyToEvent = (event: Electron.IpcMainEvent) => {
  8. const { processId, frameId } = event;
  9. event.reply = (channel: string, ...args: any[]) => {
  10. event.sender.sendToFrame([processId, frameId], channel, ...args);
  11. };
  12. };
  13. const addReturnValueToEvent = (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent) => {
  14. Object.defineProperty(event, 'returnValue', {
  15. set: (value) => event._replyChannel.sendReply(value),
  16. get: () => {}
  17. });
  18. };
  19. const getServiceWorkerFromEvent = (event: Electron.IpcMainServiceWorkerEvent | Electron.IpcMainServiceWorkerInvokeEvent): ServiceWorkerMain | undefined => {
  20. return event.session.serviceWorkers._getWorkerFromVersionIDIfExists(event.versionId);
  21. };
  22. const addServiceWorkerPropertyToEvent = (event: Electron.IpcMainServiceWorkerEvent | Electron.IpcMainServiceWorkerInvokeEvent) => {
  23. Object.defineProperty(event, 'serviceWorker', {
  24. get: () => event.session.serviceWorkers.getWorkerFromVersionID(event.versionId)
  25. });
  26. };
  27. /**
  28. * Cached IPC emitters sorted by dispatch priority.
  29. * Caching is used to avoid frequent array allocations.
  30. */
  31. const cachedIpcEmitters: (ElectronInternal.IpcMainInternal | undefined)[] = [
  32. undefined, // WebFrameMain ipc
  33. undefined, // WebContents ipc
  34. ipcMain
  35. ];
  36. // Get list of relevant IPC emitters for dispatch.
  37. const getIpcEmittersForFrameEvent = (event: Electron.IpcMainEvent | Electron.IpcMainInvokeEvent): (ElectronInternal.IpcMainInternal | undefined)[] => {
  38. // Lookup by FrameTreeNode ID to ensure IPCs received after a frame swap are
  39. // always received. This occurs when a RenderFrame sends an IPC while it's
  40. // unloading and its internal state is pending deletion.
  41. const { frameTreeNodeId } = event;
  42. const webFrameByFtn = frameTreeNodeId ? webFrameMainBinding._fromFtnIdIfExists(frameTreeNodeId) : undefined;
  43. cachedIpcEmitters[0] = webFrameByFtn?.ipc;
  44. cachedIpcEmitters[1] = event.sender.ipc;
  45. return cachedIpcEmitters;
  46. };
  47. /**
  48. * Listens for IPC dispatch events on `api`.
  49. */
  50. export function addIpcDispatchListeners (api: NodeJS.EventEmitter) {
  51. api.on('-ipc-message' as any, function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, args: any[]) {
  52. const internal = v8Util.getHiddenValue<boolean>(event, 'internal');
  53. if (internal) {
  54. ipcMainInternal.emit(channel, event, ...args);
  55. } else if (event.type === 'frame') {
  56. addReplyToEvent(event);
  57. event.sender.emit('ipc-message', event, channel, ...args);
  58. for (const ipcEmitter of getIpcEmittersForFrameEvent(event)) {
  59. ipcEmitter?.emit(channel, event, ...args);
  60. }
  61. } else if (event.type === 'service-worker') {
  62. addServiceWorkerPropertyToEvent(event);
  63. getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, ...args);
  64. }
  65. } as any);
  66. api.on('-ipc-invoke' as any, async function (event: Electron.IpcMainInvokeEvent | Electron.IpcMainServiceWorkerInvokeEvent, channel: string, args: any[]) {
  67. const internal = v8Util.getHiddenValue<boolean>(event, 'internal');
  68. const replyWithResult = (result: any) => event._replyChannel.sendReply({ result });
  69. const replyWithError = (error: Error) => {
  70. console.error(`Error occurred in handler for '${channel}':`, error);
  71. event._replyChannel.sendReply({ error: error.toString() });
  72. };
  73. const targets: (Electron.IpcMainServiceWorker | ElectronInternal.IpcMainInternal | undefined)[] = [];
  74. if (internal) {
  75. targets.push(ipcMainInternal);
  76. } else if (event.type === 'frame') {
  77. targets.push(...getIpcEmittersForFrameEvent(event));
  78. } else if (event.type === 'service-worker') {
  79. addServiceWorkerPropertyToEvent(event);
  80. const workerIpc = getServiceWorkerFromEvent(event)?.ipc;
  81. targets.push(workerIpc);
  82. }
  83. const target = targets.find(target => (target as any)?._invokeHandlers.has(channel));
  84. if (target) {
  85. const handler = (target as any)._invokeHandlers.get(channel);
  86. try {
  87. replyWithResult(await Promise.resolve(handler(event, ...args)));
  88. } catch (err) {
  89. replyWithError(err as Error);
  90. }
  91. } else {
  92. replyWithError(new Error(`No handler registered for '${channel}'`));
  93. }
  94. } as any);
  95. api.on('-ipc-message-sync' as any, function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, args: any[]) {
  96. const internal = v8Util.getHiddenValue<boolean>(event, 'internal');
  97. addReturnValueToEvent(event);
  98. if (internal) {
  99. ipcMainInternal.emit(channel, event, ...args);
  100. } else if (event.type === 'frame') {
  101. addReplyToEvent(event);
  102. const webContents = event.sender;
  103. const ipcEmitters = getIpcEmittersForFrameEvent(event);
  104. if (
  105. webContents.listenerCount('ipc-message-sync') === 0 &&
  106. ipcEmitters.every(emitter => !emitter || emitter.listenerCount(channel) === 0)
  107. ) {
  108. console.warn(`WebContents #${webContents.id} called ipcRenderer.sendSync() with '${channel}' channel without listeners.`);
  109. }
  110. webContents.emit('ipc-message-sync', event, channel, ...args);
  111. for (const ipcEmitter of ipcEmitters) {
  112. ipcEmitter?.emit(channel, event, ...args);
  113. }
  114. } else if (event.type === 'service-worker') {
  115. addServiceWorkerPropertyToEvent(event);
  116. getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, ...args);
  117. }
  118. } as any);
  119. api.on('-ipc-message-host', function (event: Electron.IpcMainEvent, channel: string, args: any[]) {
  120. event.sender.emit('-ipc-message-host', event, channel, args);
  121. });
  122. api.on('-ipc-ports' as any, function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, message: any, ports: any[]) {
  123. event.ports = ports.map(p => new MessagePortMain(p));
  124. if (event.type === 'frame') {
  125. const ipcEmitters = getIpcEmittersForFrameEvent(event);
  126. for (const ipcEmitter of ipcEmitters) {
  127. ipcEmitter?.emit(channel, event, message);
  128. }
  129. } if (event.type === 'service-worker') {
  130. addServiceWorkerPropertyToEvent(event);
  131. getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, message);
  132. }
  133. } as any);
  134. }