fix: don't handle browser messages before document element is created (#19718)
* 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
This commit is contained in:
parent
398c5d553d
commit
04dbd5c53f
5 changed files with 90 additions and 35 deletions
|
@ -98,38 +98,61 @@ ElectronApiServiceImpl::~ElectronApiServiceImpl() = default;
|
||||||
|
|
||||||
ElectronApiServiceImpl::ElectronApiServiceImpl(
|
ElectronApiServiceImpl::ElectronApiServiceImpl(
|
||||||
content::RenderFrame* render_frame,
|
content::RenderFrame* render_frame,
|
||||||
RendererClientBase* renderer_client,
|
RendererClientBase* renderer_client)
|
||||||
mojom::ElectronRendererAssociatedRequest request)
|
|
||||||
: content::RenderFrameObserver(render_frame),
|
: content::RenderFrameObserver(render_frame),
|
||||||
binding_(this),
|
binding_(this),
|
||||||
render_frame_(render_frame),
|
renderer_client_(renderer_client),
|
||||||
renderer_client_(renderer_client) {
|
weak_factory_(this) {}
|
||||||
|
|
||||||
|
void ElectronApiServiceImpl::BindTo(
|
||||||
|
mojom::ElectronRendererAssociatedRequest request) {
|
||||||
|
// Note: BindTo might be called for multiple times.
|
||||||
|
if (binding_.is_bound())
|
||||||
|
binding_.Unbind();
|
||||||
|
|
||||||
binding_.Bind(std::move(request));
|
binding_.Bind(std::move(request));
|
||||||
binding_.set_connection_error_handler(base::BindOnce(
|
binding_.set_connection_error_handler(
|
||||||
&ElectronApiServiceImpl::OnDestruct, base::Unretained(this)));
|
base::BindOnce(&ElectronApiServiceImpl::OnConnectionError, GetWeakPtr()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
void ElectronApiServiceImpl::DidCreateDocumentElement() {
|
||||||
void ElectronApiServiceImpl::CreateMojoService(
|
document_created_ = true;
|
||||||
content::RenderFrame* render_frame,
|
|
||||||
RendererClientBase* renderer_client,
|
|
||||||
mojom::ElectronRendererAssociatedRequest request) {
|
|
||||||
DCHECK(render_frame);
|
|
||||||
|
|
||||||
// Owns itself. Will be deleted when the render frame is destroyed.
|
|
||||||
new ElectronApiServiceImpl(render_frame, renderer_client, std::move(request));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ElectronApiServiceImpl::OnDestruct() {
|
void ElectronApiServiceImpl::OnDestruct() {
|
||||||
delete this;
|
delete this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ElectronApiServiceImpl::OnConnectionError() {
|
||||||
|
if (binding_.is_bound())
|
||||||
|
binding_.Unbind();
|
||||||
|
}
|
||||||
|
|
||||||
void ElectronApiServiceImpl::Message(bool internal,
|
void ElectronApiServiceImpl::Message(bool internal,
|
||||||
bool send_to_all,
|
bool send_to_all,
|
||||||
const std::string& channel,
|
const std::string& channel,
|
||||||
base::Value arguments,
|
base::Value arguments,
|
||||||
int32_t sender_id) {
|
int32_t sender_id) {
|
||||||
blink::WebLocalFrame* frame = render_frame_->GetWebFrame();
|
// Don't handle browser messages before document element is created.
|
||||||
|
//
|
||||||
|
// Note: It is probably better to save the message and then replay it after
|
||||||
|
// document is ready, but current behavior has been there since the first
|
||||||
|
// day of Electron, and no one has complained so far.
|
||||||
|
//
|
||||||
|
// Reason 1:
|
||||||
|
// 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
|
||||||
|
// document element if it hasn't been created yet, and it makes our init
|
||||||
|
// script to run while `window.location` is still "about:blank".
|
||||||
|
// (See https://github.com/electron/electron/pull/1044.)
|
||||||
|
//
|
||||||
|
// Reason 2:
|
||||||
|
// The libuv message loop integration would be broken for unkown reasons.
|
||||||
|
// (See https://github.com/electron/electron/issues/19368.)
|
||||||
|
if (!document_created_)
|
||||||
|
return;
|
||||||
|
|
||||||
|
blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
|
||||||
if (!frame)
|
if (!frame)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "base/memory/weak_ptr.h"
|
||||||
#include "content/public/renderer/render_frame.h"
|
#include "content/public/renderer/render_frame.h"
|
||||||
#include "content/public/renderer/render_frame_observer.h"
|
#include "content/public/renderer/render_frame_observer.h"
|
||||||
#include "electron/shell/common/api/api.mojom.h"
|
#include "electron/shell/common/api/api.mojom.h"
|
||||||
|
@ -19,10 +20,10 @@ class RendererClientBase;
|
||||||
class ElectronApiServiceImpl : public mojom::ElectronRenderer,
|
class ElectronApiServiceImpl : public mojom::ElectronRenderer,
|
||||||
public content::RenderFrameObserver {
|
public content::RenderFrameObserver {
|
||||||
public:
|
public:
|
||||||
static void CreateMojoService(
|
ElectronApiServiceImpl(content::RenderFrame* render_frame,
|
||||||
content::RenderFrame* render_frame,
|
RendererClientBase* renderer_client);
|
||||||
RendererClientBase* renderer_client,
|
|
||||||
mojom::ElectronRendererAssociatedRequest request);
|
void BindTo(mojom::ElectronRendererAssociatedRequest request);
|
||||||
|
|
||||||
void Message(bool internal,
|
void Message(bool internal,
|
||||||
bool send_to_all,
|
bool send_to_all,
|
||||||
|
@ -33,19 +34,26 @@ class ElectronApiServiceImpl : public mojom::ElectronRenderer,
|
||||||
void TakeHeapSnapshot(mojo::ScopedHandle file,
|
void TakeHeapSnapshot(mojo::ScopedHandle file,
|
||||||
TakeHeapSnapshotCallback callback) override;
|
TakeHeapSnapshotCallback callback) override;
|
||||||
|
|
||||||
|
base::WeakPtr<ElectronApiServiceImpl> GetWeakPtr() {
|
||||||
|
return weak_factory_.GetWeakPtr();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
~ElectronApiServiceImpl() override;
|
~ElectronApiServiceImpl() override;
|
||||||
ElectronApiServiceImpl(content::RenderFrame* render_frame,
|
|
||||||
RendererClientBase* renderer_client,
|
|
||||||
mojom::ElectronRendererAssociatedRequest request);
|
|
||||||
|
|
||||||
// RenderFrameObserver implementation.
|
// RenderFrameObserver implementation.
|
||||||
|
void DidCreateDocumentElement() override;
|
||||||
void OnDestruct() override;
|
void OnDestruct() override;
|
||||||
|
|
||||||
|
void OnConnectionError();
|
||||||
|
|
||||||
|
// Whether the DOM document element has been created.
|
||||||
|
bool document_created_ = false;
|
||||||
|
|
||||||
mojo::AssociatedBinding<mojom::ElectronRenderer> binding_;
|
mojo::AssociatedBinding<mojom::ElectronRenderer> binding_;
|
||||||
|
|
||||||
content::RenderFrame* render_frame_;
|
|
||||||
RendererClientBase* renderer_client_;
|
RendererClientBase* renderer_client_;
|
||||||
|
base::WeakPtrFactory<ElectronApiServiceImpl> weak_factory_;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(ElectronApiServiceImpl);
|
DISALLOW_COPY_AND_ASSIGN(ElectronApiServiceImpl);
|
||||||
};
|
};
|
||||||
|
|
|
@ -227,17 +227,12 @@ void RendererClientBase::RenderFrameCreated(
|
||||||
std::make_unique<electron::PrintRenderFrameHelperDelegate>());
|
std::make_unique<electron::PrintRenderFrameHelperDelegate>());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// TODO(nornagon): it might be possible for an IPC message sent to this
|
// Note: ElectronApiServiceImpl has to be created now to capture the
|
||||||
// service to trigger v8 context creation before the page has begun loading.
|
// DidCreateDocumentElement event.
|
||||||
// However, it's unclear whether such a timing is possible to trigger, and we
|
auto* service = new ElectronApiServiceImpl(render_frame, this);
|
||||||
// don't have any test to confirm it. Add a test that confirms that a
|
|
||||||
// main->renderer IPC can't cause the preload script to be executed twice. If
|
|
||||||
// it is possible to trigger the preload script before the document is ready
|
|
||||||
// through this interface, we should delay adding it to the registry until
|
|
||||||
// the document is ready.
|
|
||||||
render_frame->GetAssociatedInterfaceRegistry()->AddInterface(
|
render_frame->GetAssociatedInterfaceRegistry()->AddInterface(
|
||||||
base::BindRepeating(&ElectronApiServiceImpl::CreateMojoService,
|
base::BindRepeating(&ElectronApiServiceImpl::BindTo,
|
||||||
render_frame, this));
|
service->GetWeakPtr()));
|
||||||
|
|
||||||
#if BUILDFLAG(ENABLE_PDF_VIEWER)
|
#if BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||||
// Allow access to file scheme from pdf viewer.
|
// Allow access to file scheme from pdf viewer.
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { AddressInfo } from 'net'
|
||||||
import * as chaiAsPromised from 'chai-as-promised'
|
import * as chaiAsPromised from 'chai-as-promised'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import * as http from 'http'
|
import * as http from 'http'
|
||||||
import { BrowserWindow, webContents } from 'electron'
|
import { BrowserWindow, ipcMain, webContents } from 'electron'
|
||||||
import { emittedOnce } from './events-helpers';
|
import { emittedOnce } from './events-helpers';
|
||||||
import { closeAllWindows } from './window-helpers';
|
import { closeAllWindows } from './window-helpers';
|
||||||
|
|
||||||
|
@ -77,6 +77,26 @@ describe('webContents module', () => {
|
||||||
w.webContents.send(null as any)
|
w.webContents.send(null as any)
|
||||||
}).to.throw('Missing required channel argument')
|
}).to.throw('Missing required channel argument')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('does not block node async APIs when sent before document is ready', (done) => {
|
||||||
|
// Please reference https://github.com/electron/electron/issues/19368 if
|
||||||
|
// this test fails.
|
||||||
|
ipcMain.once('async-node-api-done', () => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
const w = new BrowserWindow({
|
||||||
|
show: false,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
sandbox: false,
|
||||||
|
contextIsolation: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
w.loadFile(path.join(fixturesPath, 'pages', 'send-after-node.html'))
|
||||||
|
setTimeout(() => {
|
||||||
|
w.webContents.send("test")
|
||||||
|
}, 50)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('webContents.executeJavaScript', () => {
|
describe('webContents.executeJavaScript', () => {
|
||||||
|
|
9
spec/fixtures/pages/send-after-node.html
vendored
Normal file
9
spec/fixtures/pages/send-after-node.html
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script type="text/javascript" charset="utf-8">
|
||||||
|
require('fs').readdir(process.cwd(), () => {
|
||||||
|
require('electron').ipcRenderer.send('async-node-api-done');
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in a new issue