From 501ac15b1d5fc953cb312f28505026d003c6f014 Mon Sep 17 00:00:00 2001
From: Milan Burda <milan.burda@gmail.com>
Date: Wed, 25 Aug 2021 09:46:46 +0200
Subject: [PATCH] feat: add <webview>.sendToFrame() / frameId to 'ipc-message'
 event (#30451)

---
 docs/api/webview-tag.md           | 16 ++++++++++++++++
 lib/browser/guest-view-manager.ts |  8 ++++++--
 lib/common/web-view-methods.ts    |  1 +
 spec/webview-spec.js              | 21 ++++++++++++++++++++-
 4 files changed, 43 insertions(+), 3 deletions(-)

diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md
index 3efa2614a35..9f356cf1abe 100644
--- a/docs/api/webview-tag.md
+++ b/docs/api/webview-tag.md
@@ -606,6 +606,21 @@ listening to the `channel` event with the [`ipcRenderer`](ipc-renderer.md) modul
 See [webContents.send](web-contents.md#contentssendchannel-args) for
 examples.
 
+### `<webview>.sendToFrame(frameId, channel, ...args)`
+
+* `frameId` [number, number] - `[processId, frameId]`
+* `channel` String
+* `...args` any[]
+
+Returns `Promise<void>`
+
+Send an asynchronous message to renderer process via `channel`, you can also
+send arbitrary arguments. The renderer process can handle the message by
+listening to the `channel` event with the [`ipcRenderer`](ipc-renderer.md) module.
+
+See [webContents.sendToFrame](web-contents.md#contentssendtoframeframeid-channel-args) for
+examples.
+
 ### `<webview>.sendInputEvent(event)`
 
 * `event`  [MouseInputEvent](structures/mouse-input-event.md) | [MouseWheelInputEvent](structures/mouse-wheel-input-event.md) | [KeyboardInputEvent](structures/keyboard-input-event.md)
@@ -920,6 +935,7 @@ webview.addEventListener('close', () => {
 
 Returns:
 
+* `frameId` [number, number] - pair of `[processId, frameId]`.
 * `channel` String
 * `args` any[]
 
diff --git a/lib/browser/guest-view-manager.ts b/lib/browser/guest-view-manager.ts
index b2b5f49bfcb..dec9ddd4011 100644
--- a/lib/browser/guest-view-manager.ts
+++ b/lib/browser/guest-view-manager.ts
@@ -161,8 +161,12 @@ const createGuest = function (embedder: Electron.WebContents, embedderFrameId: n
   });
 
   // Dispatch guest's IPC messages to embedder.
-  guest.on('ipc-message-host' as any, function (_: Electron.Event, channel: string, args: any[]) {
-    sendToEmbedder(IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT, 'ipc-message', { channel, args });
+  guest.on('ipc-message-host' as any, function (event: Electron.IpcMainEvent, channel: string, args: any[]) {
+    sendToEmbedder(IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT, 'ipc-message', {
+      frameId: [event.processId, event.frameId],
+      channel,
+      args
+    });
   });
 
   // Notify guest of embedder window visibility when it is ready
diff --git a/lib/common/web-view-methods.ts b/lib/common/web-view-methods.ts
index 449ee9d3915..a2cfb328cc4 100644
--- a/lib/common/web-view-methods.ts
+++ b/lib/common/web-view-methods.ts
@@ -65,6 +65,7 @@ export const asyncMethods = new Set([
   'insertText',
   'removeInsertedCSS',
   'send',
+  'sendToFrame',
   'sendInputEvent',
   'setLayoutZoomLevelLimits',
   'setVisualZoomLevelLimits',
diff --git a/spec/webview-spec.js b/spec/webview-spec.js
index 1d4dd657bd6..e1d2ab3d27c 100644
--- a/spec/webview-spec.js
+++ b/spec/webview-spec.js
@@ -267,6 +267,24 @@ describe('<webview> tag', function () {
       expect(args).to.deep.equal([message]);
     });
 
+    it('<webview>.sendToFrame()', async () => {
+      loadWebView(webview, {
+        nodeintegration: 'on',
+        webpreferences: 'contextIsolation=no',
+        preload: `${fixtures}/module/preload-ipc.js`,
+        src: `file://${fixtures}/pages/ipc-message.html`
+      });
+
+      const { frameId } = await waitForEvent(webview, 'ipc-message');
+
+      const message = 'boom!';
+      webview.sendToFrame(frameId, 'ping', message);
+
+      const { channel, args } = await waitForEvent(webview, 'ipc-message');
+      expect(channel).to.equal('pong');
+      expect(args).to.deep.equal([message]);
+    });
+
     it('works without script tag in page', async () => {
       const message = await startLoadingWebViewAndWaitForMessage(webview, {
         preload: `${fixtures}/module/preload.js`,
@@ -529,8 +547,9 @@ describe('<webview> tag', function () {
         webpreferences: 'contextIsolation=no',
         src: `file://${fixtures}/pages/ipc-message.html`
       });
-      const { channel, args } = await waitForEvent(webview, 'ipc-message');
+      const { frameId, channel, args } = await waitForEvent(webview, 'ipc-message');
 
+      expect(frameId).to.be.an('array').that.has.lengthOf(2);
       expect(channel).to.equal('channel');
       expect(args).to.deep.equal(['arg1', 'arg2']);
     });