Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add event.senderId property to IPCs sent via ipcRenderer.sendTo #14395

Merged
merged 1 commit into from Sep 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 9 additions & 2 deletions atom/browser/api/atom_api_web_contents.cc
Expand Up @@ -1556,10 +1556,17 @@ void WebContents::TabTraverse(bool reverse) {
bool WebContents::SendIPCMessage(bool all_frames,
const std::string& channel,
const base::ListValue& args) {
return SendIPCMessageWithSender(all_frames, channel, args);
}

bool WebContents::SendIPCMessageWithSender(bool all_frames,
const std::string& channel,
const base::ListValue& args,
int32_t sender_id) {
auto* frame_host = web_contents()->GetMainFrame();
if (frame_host) {
return frame_host->Send(new AtomFrameMsg_Message(
frame_host->GetRoutingID(), all_frames, channel, args));
frame_host->GetRoutingID(), all_frames, channel, args, sender_id));
}
return false;
}
Expand Down Expand Up @@ -2090,7 +2097,7 @@ void WebContents::OnRendererMessageTo(content::RenderFrameHost* frame_host,
isolate(), web_contents_id);

if (web_contents) {
web_contents->SendIPCMessage(send_to_all, channel, args);
web_contents->SendIPCMessageWithSender(send_to_all, channel, args, ID());
}
}

Expand Down
5 changes: 5 additions & 0 deletions atom/browser/api/atom_api_web_contents.h
Expand Up @@ -181,6 +181,11 @@ class WebContents : public mate::TrackableObject<WebContents>,
const std::string& channel,
const base::ListValue& args);

bool SendIPCMessageWithSender(bool all_frames,
const std::string& channel,
const base::ListValue& args,
int32_t sender_id = 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not add this default fourth arg to SendIPCMessage() instead of adding a new function -- do we need both functions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does not work for methods exposed to JS via mate. The default arguments are ignored. It has to be a separate method unfortunatelly


// Send WebInputEvent to the page.
void SendInputEvent(v8::Isolate* isolate, v8::Local<v8::Value> input_event);

Expand Down
5 changes: 3 additions & 2 deletions atom/common/api/api_messages.h
Expand Up @@ -39,10 +39,11 @@ IPC_MESSAGE_ROUTED4(AtomFrameHostMsg_Message_To,
std::string /* channel */,
base::ListValue /* arguments */)

IPC_MESSAGE_ROUTED3(AtomFrameMsg_Message,
IPC_MESSAGE_ROUTED4(AtomFrameMsg_Message,
bool /* send_to_all */,
std::string /* channel */,
base::ListValue /* arguments */)
base::ListValue /* arguments */,
int32_t /* sender_id */)

IPC_MESSAGE_ROUTED0(AtomViewMsg_Offscreen)

Expand Down
3 changes: 2 additions & 1 deletion atom/common/api/remote_callback_freer.cc
Expand Up @@ -36,12 +36,13 @@ RemoteCallbackFreer::~RemoteCallbackFreer() {}
void RemoteCallbackFreer::RunDestructor() {
auto* channel = "ELECTRON_RENDERER_RELEASE_CALLBACK";
base::ListValue args;
int32_t sender_id = 0;
args.AppendString(context_id_);
args.AppendInteger(object_id_);
auto* frame_host = web_contents()->GetMainFrame();
if (frame_host) {
frame_host->Send(new AtomFrameMsg_Message(frame_host->GetRoutingID(), false,
channel, args));
channel, args, sender_id));
}

Observe(nullptr);
Expand Down
11 changes: 7 additions & 4 deletions atom/renderer/atom_render_frame_observer.cc
Expand Up @@ -169,7 +169,8 @@ bool AtomRenderFrameObserver::OnMessageReceived(const IPC::Message& message) {

void AtomRenderFrameObserver::OnBrowserMessage(bool send_to_all,
const std::string& channel,
const base::ListValue& args) {
const base::ListValue& args,
int32_t sender_id) {
// Don't handle browser messages before document element is created.
// When we receive a message from the browser, we try to transfer it
// to a web page, and when we do that Blink creates an empty
Expand All @@ -182,21 +183,22 @@ void AtomRenderFrameObserver::OnBrowserMessage(bool send_to_all,
if (!frame || !render_frame_->IsMainFrame())
return;

EmitIPCEvent(frame, channel, args);
EmitIPCEvent(frame, channel, args, sender_id);

// Also send the message to all sub-frames.
if (send_to_all) {
for (blink::WebFrame* child = frame->FirstChild(); child;
child = child->NextSibling())
if (child->IsWebLocalFrame()) {
EmitIPCEvent(child->ToWebLocalFrame(), channel, args);
EmitIPCEvent(child->ToWebLocalFrame(), channel, args, sender_id);
}
}
}

void AtomRenderFrameObserver::EmitIPCEvent(blink::WebLocalFrame* frame,
const std::string& channel,
const base::ListValue& args) {
const base::ListValue& args,
int32_t sender_id) {
if (!frame)
return;

Expand All @@ -218,6 +220,7 @@ void AtomRenderFrameObserver::EmitIPCEvent(blink::WebLocalFrame* frame,
// Insert the Event object, event.sender is ipc.
mate::Dictionary event = mate::Dictionary::CreateEmpty(isolate);
event.Set("sender", ipc);
event.Set("senderId", sender_id);
args_vector.insert(args_vector.begin(), event.GetHandle());
mate::EmitEvent(isolate, ipc, channel, args_vector);
}
Expand Down
6 changes: 4 additions & 2 deletions atom/renderer/atom_render_frame_observer.h
Expand Up @@ -45,7 +45,8 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver {
protected:
virtual void EmitIPCEvent(blink::WebLocalFrame* frame,
const std::string& channel,
const base::ListValue& args);
const base::ListValue& args,
int32_t sender_id);

private:
bool ShouldNotifyClient(int world_id);
Expand All @@ -54,7 +55,8 @@ class AtomRenderFrameObserver : public content::RenderFrameObserver {
bool IsIsolatedWorld(int world_id);
void OnBrowserMessage(bool send_to_all,
const std::string& channel,
const base::ListValue& args);
const base::ListValue& args,
int32_t sender_id);

content::RenderFrame* render_frame_;
RendererClientBase* renderer_client_;
Expand Down
6 changes: 4 additions & 2 deletions atom/renderer/atom_sandboxed_renderer_client.cc
Expand Up @@ -107,7 +107,8 @@ class AtomSandboxedRenderFrameObserver : public AtomRenderFrameObserver {
protected:
void EmitIPCEvent(blink::WebLocalFrame* frame,
const std::string& channel,
const base::ListValue& args) override {
const base::ListValue& args,
int32_t sender_id) override {
if (!frame)
return;

Expand All @@ -116,7 +117,8 @@ class AtomSandboxedRenderFrameObserver : public AtomRenderFrameObserver {
auto context = frame->MainWorldScriptContext();
v8::Context::Scope context_scope(context);
v8::Local<v8::Value> argv[] = {mate::ConvertToV8(isolate, channel),
mate::ConvertToV8(isolate, args)};
mate::ConvertToV8(isolate, args),
mate::ConvertToV8(isolate, sender_id)};
renderer_client_->InvokeIpcCallback(
context, "onMessage",
std::vector<v8::Local<v8::Value>>(argv, argv + node::arraysize(argv)));
Expand Down
14 changes: 14 additions & 0 deletions docs/api/ipc-renderer.md
Expand Up @@ -89,3 +89,17 @@ Sends a message to a window with `windowid` via `channel`.

Like `ipcRenderer.send` but the event will be sent to the `<webview>` element in
the host page instead of the main process.

## Event object

The `event` object passed to the `callback` has the following methods:

### `event.senderId`

Returns the `webContents.id` that sent the message, you can call
`event.sender.sendTo(event.senderId, ...)` to reply to the message, see
[ipcRenderer.sendTo][ipc-renderer-sendto] for more information.
This only applies to messages sent from a different renderer.
Messages sent directly from the main process set `event.senderId` to `0`.

[ipc-renderer-sendto]: #ipcrenderersendtowindowid-channel--arg1-arg2-
4 changes: 2 additions & 2 deletions lib/sandboxed_renderer/api/ipc-renderer.js
Expand Up @@ -9,8 +9,8 @@ const ipcNative = process.atomBinding('ipc')
// private/public APIs.
v8Util.setHiddenValue(global, 'ipcNative', ipcNative)

ipcNative.onMessage = function (channel, args) {
ipcRenderer.emit(channel, {sender: ipcRenderer}, ...args)
ipcNative.onMessage = function (channel, args, senderId) {
ipcRenderer.emit(channel, {sender: ipcRenderer, senderId}, ...args)
}

ipcNative.onExit = function () {
Expand Down
46 changes: 36 additions & 10 deletions spec/api-ipc-renderer-spec.js
Expand Up @@ -132,39 +132,65 @@ describe('ipc renderer module', () => {
describe('ipcRenderer.sendTo', () => {
let contents = null

beforeEach(() => { contents = webContents.create({}) })

afterEach(() => {
ipcRenderer.removeAllListeners('pong')
contents.destroy()
contents = null
})

it('sends message to WebContents', done => {
const webContentsId = remote.getCurrentWebContents().id
contents = webContents.create({
preload: path.join(fixtures, 'module', 'preload-inject-ipc.js')
})

const payload = 'Hello World!'

ipcRenderer.once('pong', (event, id) => {
expect(webContentsId).to.equal(id)
ipcRenderer.once('pong', (event, data) => {
expect(payload).to.equal(data)
done()
})

contents.once('did-finish-load', () => {
ipcRenderer.sendTo(contents.id, 'ping', webContentsId)
ipcRenderer.sendTo(contents.id, 'ping', payload)
})

contents.loadURL(`file://${path.join(fixtures, 'pages', 'ping-pong.html')}`)
})

it('sends message to WebContents (sanboxed renderer)', done => {
contents = webContents.create({
preload: path.join(fixtures, 'module', 'preload-inject-ipc.js'),
sandbox: true
})

const payload = 'Hello World!'

ipcRenderer.once('pong', (event, data) => {
expect(payload).to.equal(data)
done()
})

contents.once('did-finish-load', () => {
ipcRenderer.sendTo(contents.id, 'ping', payload)
})

contents.loadURL(`file://${path.join(fixtures, 'pages', 'ping-pong.html')}`)
})

it('sends message to WebContents (channel has special chars)', done => {
const webContentsId = remote.getCurrentWebContents().id
contents = webContents.create({
preload: path.join(fixtures, 'module', 'preload-inject-ipc.js')
})

const payload = 'Hello World!'

ipcRenderer.once('pong-æøåü', (event, id) => {
expect(webContentsId).to.equal(id)
ipcRenderer.once('pong-æøåü', (event, data) => {
expect(payload).to.equal(data)
done()
})

contents.once('did-finish-load', () => {
ipcRenderer.sendTo(contents.id, 'ping-æøåü', webContentsId)
ipcRenderer.sendTo(contents.id, 'ping-æøåü', payload)
})

contents.loadURL(`file://${path.join(fixtures, 'pages', 'ping-pong.html')}`)
Expand Down
10 changes: 4 additions & 6 deletions spec/fixtures/pages/ping-pong.html
@@ -1,14 +1,12 @@
<html>
<body>
<script type="text/javascript" charset="utf-8">
const {ipcRenderer} = require('electron')
ipcRenderer.on('ping', function (event, id) {
ipcRenderer.sendTo(id, 'pong', id)
ipcRenderer.on('ping', function (event, payload) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ipcRenderer is probably undefined here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's exposed by the preload script

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preload: path.join(fixtures, 'module', 'preload-inject-ipc.js'),

ipcRenderer.sendTo(event.senderId, 'pong', payload)
})
ipcRenderer.on('ping-æøåü', function (event, id) {
ipcRenderer.sendTo(id, 'pong-æøåü', id)
ipcRenderer.on('ping-æøåü', function (event, payload) {
ipcRenderer.sendTo(event.senderId, 'pong-æøåü', payload)
})
</script>
</body>
</html>