Skip to content

Commit 98d78f2

Browse files
zcbenzMarshallOfSound
authored andcommitted
fix: don't handle browser messages before document element is created (6-0-x) (electron#19719)
* fix: don't handle browser messages before document element is created * fix: bind ElectronApiServiceImpl later DidCreateDocumentElement is called before the ElectronApiServiceImpl gets bound. * chore: add comment
1 parent a8e70a8 commit 98d78f2

File tree

5 files changed

+86
-35
lines changed

5 files changed

+86
-35
lines changed

atom/renderer/electron_api_service_impl.cc

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -98,38 +98,61 @@ ElectronApiServiceImpl::~ElectronApiServiceImpl() = default;
9898

9999
ElectronApiServiceImpl::ElectronApiServiceImpl(
100100
content::RenderFrame* render_frame,
101-
RendererClientBase* renderer_client,
102-
mojom::ElectronRendererAssociatedRequest request)
101+
RendererClientBase* renderer_client)
103102
: content::RenderFrameObserver(render_frame),
104103
binding_(this),
105-
render_frame_(render_frame),
106-
renderer_client_(renderer_client) {
107-
binding_.Bind(std::move(request));
108-
binding_.set_connection_error_handler(base::BindOnce(
109-
&ElectronApiServiceImpl::OnDestruct, base::Unretained(this)));
110-
}
104+
renderer_client_(renderer_client),
105+
weak_factory_(this) {}
111106

112-
// static
113-
void ElectronApiServiceImpl::CreateMojoService(
114-
content::RenderFrame* render_frame,
115-
RendererClientBase* renderer_client,
107+
void ElectronApiServiceImpl::BindTo(
116108
mojom::ElectronRendererAssociatedRequest request) {
117-
DCHECK(render_frame);
109+
// Note: BindTo might be called for multiple times.
110+
if (binding_.is_bound())
111+
binding_.Unbind();
112+
113+
binding_.Bind(std::move(request));
114+
binding_.set_connection_error_handler(
115+
base::BindOnce(&ElectronApiServiceImpl::OnConnectionError, GetWeakPtr()));
116+
}
118117

119-
// Owns itself. Will be deleted when the render frame is destroyed.
120-
new ElectronApiServiceImpl(render_frame, renderer_client, std::move(request));
118+
void ElectronApiServiceImpl::DidCreateDocumentElement() {
119+
document_created_ = true;
121120
}
122121

123122
void ElectronApiServiceImpl::OnDestruct() {
124123
delete this;
125124
}
126125

126+
void ElectronApiServiceImpl::OnConnectionError() {
127+
if (binding_.is_bound())
128+
binding_.Unbind();
129+
}
130+
127131
void ElectronApiServiceImpl::Message(bool internal,
128132
bool send_to_all,
129133
const std::string& channel,
130134
base::Value arguments,
131135
int32_t sender_id) {
132-
blink::WebLocalFrame* frame = render_frame_->GetWebFrame();
136+
// Don't handle browser messages before document element is created.
137+
//
138+
// Note: It is probably better to save the message and then replay it after
139+
// document is ready, but current behavior has been there since the first
140+
// day of Electron, and no one has complained so far.
141+
//
142+
// Reason 1:
143+
// When we receive a message from the browser, we try to transfer it
144+
// to a web page, and when we do that Blink creates an empty
145+
// document element if it hasn't been created yet, and it makes our init
146+
// script to run while `window.location` is still "about:blank".
147+
// (See https://github.com/electron/electron/pull/1044.)
148+
//
149+
// Reason 2:
150+
// The libuv message loop integration would be broken for unkown reasons.
151+
// (See https://github.com/electron/electron/issues/19368.)
152+
if (!document_created_)
153+
return;
154+
155+
blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
133156
if (!frame)
134157
return;
135158

atom/renderer/electron_api_service_impl.h

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <string>
99

10+
#include "base/memory/weak_ptr.h"
1011
#include "content/public/renderer/render_frame.h"
1112
#include "content/public/renderer/render_frame_observer.h"
1213
#include "electron/atom/common/api/api.mojom.h"
@@ -19,10 +20,10 @@ class RendererClientBase;
1920
class ElectronApiServiceImpl : public mojom::ElectronRenderer,
2021
public content::RenderFrameObserver {
2122
public:
22-
static void CreateMojoService(
23-
content::RenderFrame* render_frame,
24-
RendererClientBase* renderer_client,
25-
mojom::ElectronRendererAssociatedRequest request);
23+
ElectronApiServiceImpl(content::RenderFrame* render_frame,
24+
RendererClientBase* renderer_client);
25+
26+
void BindTo(mojom::ElectronRendererAssociatedRequest request);
2627

2728
void Message(bool internal,
2829
bool send_to_all,
@@ -33,19 +34,26 @@ class ElectronApiServiceImpl : public mojom::ElectronRenderer,
3334
void TakeHeapSnapshot(mojo::ScopedHandle file,
3435
TakeHeapSnapshotCallback callback) override;
3536

37+
base::WeakPtr<ElectronApiServiceImpl> GetWeakPtr() {
38+
return weak_factory_.GetWeakPtr();
39+
}
40+
3641
private:
3742
~ElectronApiServiceImpl() override;
38-
ElectronApiServiceImpl(content::RenderFrame* render_frame,
39-
RendererClientBase* renderer_client,
40-
mojom::ElectronRendererAssociatedRequest request);
4143

4244
// RenderFrameObserver implementation.
45+
void DidCreateDocumentElement() override;
4346
void OnDestruct() override;
4447

48+
void OnConnectionError();
49+
50+
// Whether the DOM document element has been created.
51+
bool document_created_ = false;
52+
4553
mojo::AssociatedBinding<mojom::ElectronRenderer> binding_;
4654

47-
content::RenderFrame* render_frame_;
4855
RendererClientBase* renderer_client_;
56+
base::WeakPtrFactory<ElectronApiServiceImpl> weak_factory_;
4957

5058
DISALLOW_COPY_AND_ASSIGN(ElectronApiServiceImpl);
5159
};

atom/renderer/renderer_client_base.cc

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -226,17 +226,12 @@ void RendererClientBase::RenderFrameCreated(
226226
render_frame, std::make_unique<atom::PrintRenderFrameHelperDelegate>());
227227
#endif
228228

229-
// TODO(nornagon): it might be possible for an IPC message sent to this
230-
// service to trigger v8 context creation before the page has begun loading.
231-
// However, it's unclear whether such a timing is possible to trigger, and we
232-
// don't have any test to confirm it. Add a test that confirms that a
233-
// main->renderer IPC can't cause the preload script to be executed twice. If
234-
// it is possible to trigger the preload script before the document is ready
235-
// through this interface, we should delay adding it to the registry until
236-
// the document is ready.
229+
// Note: ElectronApiServiceImpl has to be created now to capture the
230+
// DidCreateDocumentElement event.
231+
auto* service = new ElectronApiServiceImpl(render_frame, this);
237232
render_frame->GetAssociatedInterfaceRegistry()->AddInterface(
238-
base::BindRepeating(&ElectronApiServiceImpl::CreateMojoService,
239-
render_frame, this));
233+
base::BindRepeating(&ElectronApiServiceImpl::BindTo,
234+
service->GetWeakPtr()));
240235

241236
#if BUILDFLAG(ENABLE_PDF_VIEWER)
242237
// Allow access to file scheme from pdf viewer.

spec-main/api-web-contents-spec.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as chai from 'chai'
22
import * as chaiAsPromised from 'chai-as-promised'
33
import * as path from 'path'
4-
import { BrowserWindow, webContents } from 'electron'
4+
import { BrowserWindow, ipcMain, webContents } from 'electron'
55
import { emittedOnce } from './events-helpers';
66
import { closeWindow } from './window-helpers';
77

@@ -22,6 +22,8 @@ describe('webContents module', () => {
2222
webPreferences: {
2323
backgroundThrottling: false,
2424
nodeIntegration: true,
25+
sandbox: false,
26+
contextIsolation: false,
2527
webviewTag: true
2628
}
2729
})
@@ -52,4 +54,18 @@ describe('webContents module', () => {
5254
expect(all[all.length - 1].getType()).to.equal('remote')
5355
})
5456
})
57+
58+
describe('webContents.send(channel, args...)', () => {
59+
it('does not block node async APIs when sent before document is ready', (done) => {
60+
// Please reference https://github.com/electron/electron/issues/19368 if
61+
// this test fails.
62+
ipcMain.once('async-node-api-done', () => {
63+
done()
64+
})
65+
w.loadFile(path.join(fixturesPath, 'pages', 'send-after-node.html'))
66+
setTimeout(() => {
67+
w.webContents.send("test")
68+
}, 50)
69+
})
70+
})
5571
})
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<html>
2+
<body>
3+
<script type="text/javascript" charset="utf-8">
4+
require('fs').readdir(process.cwd(), () => {
5+
require('electron').ipcRenderer.send('async-node-api-done');
6+
})
7+
</script>
8+
</body>
9+
</html>

0 commit comments

Comments
 (0)