refactor: use v8 serialization for ipc (#20214)

* refactor: use v8 serialization for ipc

* cloning process.env doesn't work

* serialize host objects by enumerating key/values

* new serialization can handle NaN, Infinity, and undefined correctly

* can't allocate v8 objects during GC

* backport microtasks fix

* fix compile

* fix node_stream_loader reentrancy

* update subframe spec to expect undefined instead of null

* write undefined instead of crashing when serializing host objects

* fix webview spec

* fix download spec

* buffers are transformed into uint8arrays

* can't serialize promises

* fix chrome.i18n.getMessage

* fix devtools tests

* fix zoom test

* fix debug build

* fix lint

* update ipcRenderer tests

* fix printToPDF test

* update patch

* remove accidentally re-added remote-side spec

* wip

* don't attempt to serialize host objects

* jump through different hoops to set options.webContents sometimes

* whoops

* fix lint

* clean up error-handling logic

* fix memory leak

* fix lint

* convert host objects using old base::Value serialization

* fix lint more

* fall back to base::Value-based serialization

* remove commented-out code

* add docs to breaking-changes.md

* Update breaking-changes.md

* update ipcRenderer and WebContents docs

* lint

* use named values for format tag

* save a memcpy for ~30% speedup

* get rid of calls to ShallowClone

* extra debugging for paranoia

* d'oh, use the correct named tags

* apparently msstl doesn't like this DCHECK

* funny story about that DCHECK

* disable remote-related functions when enable_remote_module = false

* nits

* use EnableIf to disable remote methods in mojom

* fix include

* review comments
This commit is contained in:
Jeremy Apthorp 2019-10-09 10:59:08 -07:00 committed by John Kleinschmidt
parent c250cd6e7c
commit 2fad53e66b
38 changed files with 623 additions and 169 deletions

View file

@ -6,6 +6,59 @@ Breaking changes will be documented here, and deprecation warnings added to JS c
The `FIXME` string is used in code comments to denote things that should be fixed for future releases. See https://github.com/electron/electron/search?q=fixme The `FIXME` string is used in code comments to denote things that should be fixed for future releases. See https://github.com/electron/electron/search?q=fixme
## Planned Breaking API Changes (8.0)
### Values sent over IPC are now serialized with Structured Clone Algorithm
The algorithm used to serialize objects sent over IPC (through
`ipcRenderer.send`, `ipcRenderer.sendSync`, `WebContents.send` and related
methods) has been switched from a custom algorithm to V8's built-in [Structured
Clone Algorithm][SCA], the same algorithm used to serialize messages for
`postMessage`. This brings about a 2x performance improvement for large
messages, but also brings some breaking changes in behavior.
- Sending Functions, Promises, WeakMaps, WeakSets, or objects containing any
such values, over IPC will now throw an exception, instead of silently
converting the functions to `undefined`.
```js
// Previously:
ipcRenderer.send('channel', { value: 3, someFunction: () => {} })
// => results in { value: 3 } arriving in the main process
// From Electron 8:
ipcRenderer.send('channel', { value: 3, someFunction: () => {} })
// => throws Error("() => {} could not be cloned.")
```
- `NaN`, `Infinity` and `-Infinity` will now be correctly serialized, instead
of being converted to `null`.
- Objects containing cyclic references will now be correctly serialized,
instead of being converted to `null`.
- `Set`, `Map`, `Error` and `RegExp` values will be correctly serialized,
instead of being converted to `{}`.
- `BigInt` values will be correctly serialized, instead of being converted to
`null`.
- Sparse arrays will be serialized as such, instead of being converted to dense
arrays with `null`s.
- `Date` objects will be transferred as `Date` objects, instead of being
converted to their ISO string representation.
- Typed Arrays (such as `Uint8Array`, `Uint16Array`, `Uint32Array` and so on)
will be transferred as such, instead of being converted to Node.js `Buffer`.
- Node.js `Buffer` objects will be transferred as `Uint8Array`s. You can
convert a `Uint8Array` back to a Node.js `Buffer` by wrapping the underlying
`ArrayBuffer`:
```js
Buffer.from(value.buffer, value.byteOffset, value.byteLength)
```
Sending any objects that aren't native JS types, such as DOM objects (e.g.
`Element`, `Location`, `DOMMatrix`), Node.js objects (e.g. `process.env`,
`Stream`), or Electron objects (e.g. `WebContents`, `BrowserWindow`,
`WebFrame`) is deprecated. In Electron 8, these objects will be serialized as
before with a DeprecationWarning message, but starting in Electron 9, sending
these kinds of objects will throw a 'could not be cloned' error.
[SCA]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
## Planned Breaking API Changes (7.0) ## Planned Breaking API Changes (7.0)
### Node Headers URL ### Node Headers URL

View file

@ -55,9 +55,15 @@ Removes all listeners, or those of the specified `channel`.
* `channel` String * `channel` String
* `...args` any[] * `...args` any[]
Send a message to the main process asynchronously via `channel`, you can also Send an asynchronous message to the main process via `channel`, along with
send arbitrary arguments. Arguments will be serialized as JSON internally and arguments. Arguments will be serialized with the [Structured Clone
hence no functions or prototype chain will be included. Algorithm][SCA], just like [`postMessage`][], so prototype chains will not be
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
throw an exception.
> **NOTE**: Sending non-standard JavaScript types such as DOM objects or
> special Electron objects is deprecated, and will begin throwing an exception
> starting with Electron 9.
The main process handles it by listening for `channel` with the The main process handles it by listening for `channel` with the
[`ipcMain`](ipc-main.md) module. [`ipcMain`](ipc-main.md) module.
@ -69,9 +75,15 @@ The main process handles it by listening for `channel` with the
Returns `Promise<any>` - Resolves with the response from the main process. Returns `Promise<any>` - Resolves with the response from the main process.
Send a message to the main process asynchronously via `channel` and expect an Send a message to the main process via `channel` and expect a result
asynchronous result. Arguments will be serialized as JSON internally and asynchronously. Arguments will be serialized with the [Structured Clone
hence no functions or prototype chain will be included. Algorithm][SCA], just like [`postMessage`][], so prototype chains will not be
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
throw an exception.
> **NOTE**: Sending non-standard JavaScript types such as DOM objects or
> special Electron objects is deprecated, and will begin throwing an exception
> starting with Electron 9.
The main process should listen for `channel` with The main process should listen for `channel` with
[`ipcMain.handle()`](ipc-main.md#ipcmainhandlechannel-listener). [`ipcMain.handle()`](ipc-main.md#ipcmainhandlechannel-listener).
@ -97,15 +109,23 @@ ipcMain.handle('some-name', async (event, someArgument) => {
Returns `any` - The value sent back by the [`ipcMain`](ipc-main.md) handler. Returns `any` - The value sent back by the [`ipcMain`](ipc-main.md) handler.
Send a message to the main process synchronously via `channel`, you can also Send a message to the main process via `channel` and expect a result
send arbitrary arguments. Arguments will be serialized in JSON internally and synchronously. Arguments will be serialized with the [Structured Clone
hence no functions or prototype chain will be included. Algorithm][SCA], just like [`postMessage`][], so prototype chains will not be
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
throw an exception.
> **NOTE**: Sending non-standard JavaScript types such as DOM objects or
> special Electron objects is deprecated, and will begin throwing an exception
> starting with Electron 9.
The main process handles it by listening for `channel` with [`ipcMain`](ipc-main.md) module, The main process handles it by listening for `channel` with [`ipcMain`](ipc-main.md) module,
and replies by setting `event.returnValue`. and replies by setting `event.returnValue`.
**Note:** Sending a synchronous message will block the whole renderer process, > :warning: **WARNING**: Sending a synchronous message will block the whole
unless you know what you are doing you should never use it. > renderer process until the reply is received, so use this method only as a
> last resort. It's much better to use the asynchronous version,
> [`invoke()`](ipc-renderer.md#ipcrendererinvokechannel-args).
### `ipcRenderer.sendTo(webContentsId, channel, ...args)` ### `ipcRenderer.sendTo(webContentsId, channel, ...args)`
@ -129,3 +149,5 @@ The documentation for the `event` object passed to the `callback` can be found
in the [`ipc-renderer-event`](structures/ipc-renderer-event.md) structure docs. in the [`ipc-renderer-event`](structures/ipc-renderer-event.md) structure docs.
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter [event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter
[SCA]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
[`postMessage`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage

View file

@ -1479,9 +1479,15 @@ Opens the developer tools for the service worker context.
* `channel` String * `channel` String
* `...args` any[] * `...args` any[]
Send an asynchronous message to renderer process via `channel`, you can also Send an asynchronous message to the renderer process via `channel`, along with
send arbitrary arguments. Arguments will be serialized in JSON internally and arguments. Arguments will be serialized with the [Structured Clone
hence no functions or prototype chain will be included. Algorithm][SCA], just like [`postMessage`][], so prototype chains will not be
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
throw an exception.
> **NOTE**: Sending non-standard JavaScript types such as DOM objects or
> special Electron objects is deprecated, and will begin throwing an exception
> starting with Electron 9.
The renderer process can handle the message by listening to `channel` with the The renderer process can handle the message by listening to `channel` with the
[`ipcRenderer`](ipc-renderer.md) module. [`ipcRenderer`](ipc-renderer.md) module.
@ -1522,8 +1528,14 @@ app.on('ready', () => {
* `...args` any[] * `...args` any[]
Send an asynchronous message to a specific frame in a renderer process via Send an asynchronous message to a specific frame in a renderer process via
`channel`. Arguments will be serialized `channel`, along with arguments. Arguments will be serialized with the
as JSON internally and as such no functions or prototype chains will be included. [Structured Clone Algorithm][SCA], just like [`postMessage`][], so prototype
chains will not be included. Sending Functions, Promises, Symbols, WeakMaps, or
WeakSets will throw an exception.
> **NOTE**: Sending non-standard JavaScript types such as DOM objects or
> special Electron objects is deprecated, and will begin throwing an exception
> starting with Electron 9.
The renderer process can handle the message by listening to `channel` with the The renderer process can handle the message by listening to `channel` with the
[`ipcRenderer`](ipc-renderer.md) module. [`ipcRenderer`](ipc-renderer.md) module.
@ -1785,3 +1797,5 @@ A [`Debugger`](debugger.md) instance for this webContents.
[keyboardevent]: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent [keyboardevent]: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter [event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter
[SCA]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
[`postMessage`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage

View file

@ -483,6 +483,7 @@ filenames = {
"shell/common/gin_converters/net_converter.cc", "shell/common/gin_converters/net_converter.cc",
"shell/common/gin_converters/net_converter.h", "shell/common/gin_converters/net_converter.h",
"shell/common/gin_converters/std_converter.h", "shell/common/gin_converters/std_converter.h",
"shell/common/gin_converters/blink_converter_gin_adapter.h",
"shell/common/gin_converters/value_converter_gin_adapter.h", "shell/common/gin_converters/value_converter_gin_adapter.h",
"shell/common/gin_helper/callback.cc", "shell/common/gin_helper/callback.cc",
"shell/common/gin_helper/callback.h", "shell/common/gin_helper/callback.h",

View file

@ -240,7 +240,7 @@ const getMessagesPath = (extensionId) => {
ipcMainUtils.handleSync('CHROME_GET_MESSAGES', async function (event, extensionId) { ipcMainUtils.handleSync('CHROME_GET_MESSAGES', async function (event, extensionId) {
const messagesPath = getMessagesPath(extensionId) const messagesPath = getMessagesPath(extensionId)
return fs.promises.readFile(messagesPath) return fs.promises.readFile(messagesPath, 'utf8')
}) })
const validStorageTypes = new Set(['sync', 'local']) const validStorageTypes = new Set(['sync', 'local'])

View file

@ -23,7 +23,6 @@ const supportedWebViewEvents = [
'devtools-opened', 'devtools-opened',
'devtools-closed', 'devtools-closed',
'devtools-focused', 'devtools-focused',
'new-window',
'will-navigate', 'will-navigate',
'did-start-navigation', 'did-start-navigation',
'did-navigate', 'did-navigate',
@ -48,6 +47,13 @@ const supportedWebViewEvents = [
const guestInstances = {} const guestInstances = {}
const embedderElementsMap = {} const embedderElementsMap = {}
function sanitizeOptionsForGuest (options) {
const ret = { ...options }
// WebContents values can't be sent over IPC.
delete ret.webContents
return ret
}
// Create a new guest instance. // Create a new guest instance.
const createGuest = function (embedder, params) { const createGuest = function (embedder, params) {
if (webViewManager == null) { if (webViewManager == null) {
@ -114,6 +120,12 @@ const createGuest = function (embedder, params) {
fn(event) fn(event)
} }
guest.on('new-window', function (event, url, frameName, disposition, options, additionalFeatures, referrer) {
sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', 'new-window', url,
frameName, disposition, sanitizeOptionsForGuest(options),
additionalFeatures, referrer)
})
// Dispatch guest's IPC messages to embedder. // Dispatch guest's IPC messages to embedder.
guest.on('ipc-message-host', function (_, channel, args) { guest.on('ipc-message-host', function (_, channel, args) {
sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE', channel, ...args) sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE', channel, ...args)

View file

@ -250,8 +250,7 @@ ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, fra
// Routed window.open messages with fully parsed options // Routed window.open messages with fully parsed options
ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', function (event, url, referrer, ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', function (event, url, referrer,
frameName, disposition, options, frameName, disposition, options, additionalFeatures, postData) {
additionalFeatures, postData) {
options = mergeBrowserWindowOptions(event.sender, options) options = mergeBrowserWindowOptions(event.sender, options)
event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer) event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer)
const { newGuest } = event const { newGuest } = event

View file

@ -121,7 +121,7 @@ ipcMainUtils.handleSync('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event)
process: { process: {
arch: process.arch, arch: process.arch,
platform: process.platform, platform: process.platform,
env: process.env, env: { ...process.env },
version: process.version, version: process.version,
versions: process.versions, versions: process.versions,
execPath: process.helperExecPath execPath: process.helperExecPath

View file

@ -78,4 +78,5 @@ expose_setuseragent_on_networkcontext.patch
feat_add_set_theme_source_to_allow_apps_to.patch feat_add_set_theme_source_to_allow_apps_to.patch
revert_cleanup_remove_menu_subtitles_sublabels.patch revert_cleanup_remove_menu_subtitles_sublabels.patch
ui_views_fix_jumbo_build.patch ui_views_fix_jumbo_build.patch
export_fetchapi_mojo_traits_to_fix_component_build.patch
fix_windows_build.patch fix_windows_build.patch

View file

@ -0,0 +1,36 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jeremy Apthorp <jeremya@chromium.org>
Date: Fri, 20 Sep 2019 16:44:18 -0400
Subject: export FetchAPI mojo traits to fix component build
Without these, we get link errors in the component build when using the
blink::CloneableMessage mojo traits.
diff --git a/third_party/blink/public/common/fetch/fetch_api_request_body_mojom_traits.h b/third_party/blink/public/common/fetch/fetch_api_request_body_mojom_traits.h
index 1ddfc2108f9a0104247ea2559b597552cd20a342..f9a10b5b428e29a8824b87616f19292b38e38024 100644
--- a/third_party/blink/public/common/fetch/fetch_api_request_body_mojom_traits.h
+++ b/third_party/blink/public/common/fetch/fetch_api_request_body_mojom_traits.h
@@ -11,12 +11,13 @@
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "services/network/public/cpp/resource_request_body.h"
#include "services/network/public/mojom/url_loader.mojom-forward.h"
+#include "third_party/blink/public/common/common_export.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-forward.h"
namespace mojo {
template <>
-struct StructTraits<blink::mojom::FetchAPIRequestBodyDataView,
+struct BLINK_COMMON_EXPORT StructTraits<blink::mojom::FetchAPIRequestBodyDataView,
scoped_refptr<network::ResourceRequestBody>> {
static bool IsNull(const scoped_refptr<network::ResourceRequestBody>& r) {
return !r;
@@ -46,7 +47,7 @@ struct StructTraits<blink::mojom::FetchAPIRequestBodyDataView,
};
template <>
-struct StructTraits<blink::mojom::FetchAPIDataElementDataView,
+struct BLINK_COMMON_EXPORT StructTraits<blink::mojom::FetchAPIDataElementDataView,
network::DataElement> {
static const network::mojom::DataElementType& type(
const network::DataElement& element) {

View file

@ -12,7 +12,7 @@ native_mate, and we should remove this patch once native_mate is erased
from Electron. from Electron.
diff --git a/gin/arguments.h b/gin/arguments.h diff --git a/gin/arguments.h b/gin/arguments.h
index eaded13e2991..03e1495566d1 100644 index eaded13e29919793494dfe2f7f85fad7dcb125cf..03e1495566d1ab561dcd67517053173911288cea 100644
--- a/gin/arguments.h --- a/gin/arguments.h
+++ b/gin/arguments.h +++ b/gin/arguments.h
@@ -28,14 +28,14 @@ class GIN_EXPORT Arguments { @@ -28,14 +28,14 @@ class GIN_EXPORT Arguments {
@ -60,7 +60,7 @@ index eaded13e2991..03e1495566d1 100644
(is_for_property_ ? info_for_property_->GetReturnValue() (is_for_property_ ? info_for_property_->GetReturnValue()
: info_for_function_->GetReturnValue()) : info_for_function_->GetReturnValue())
diff --git a/gin/converter.h b/gin/converter.h diff --git a/gin/converter.h b/gin/converter.h
index 27b4d0acd016..b19209a8534a 100644 index 27b4d0acd016df378e4cb44ccda1a433244fe2c6..b19209a8534a497373c5a2f861b26502e96144c9 100644
--- a/gin/converter.h --- a/gin/converter.h
+++ b/gin/converter.h +++ b/gin/converter.h
@@ -250,7 +250,7 @@ std::enable_if_t<ToV8ReturnsMaybe<T>::value, bool> TryConvertToV8( @@ -250,7 +250,7 @@ std::enable_if_t<ToV8ReturnsMaybe<T>::value, bool> TryConvertToV8(

View file

@ -1017,7 +1017,7 @@ void WebContents::OnElectronBrowserConnectionError() {
void WebContents::Message(bool internal, void WebContents::Message(bool internal,
const std::string& channel, const std::string& channel,
base::Value arguments) { blink::CloneableMessage arguments) {
// webContents.emit('-ipc-message', new Event(), internal, channel, // webContents.emit('-ipc-message', new Event(), internal, channel,
// arguments); // arguments);
EmitWithSender("-ipc-message", bindings_.dispatch_context(), base::nullopt, EmitWithSender("-ipc-message", bindings_.dispatch_context(), base::nullopt,
@ -1026,7 +1026,7 @@ void WebContents::Message(bool internal,
void WebContents::Invoke(bool internal, void WebContents::Invoke(bool internal,
const std::string& channel, const std::string& channel,
base::Value arguments, blink::CloneableMessage arguments,
InvokeCallback callback) { InvokeCallback callback) {
// webContents.emit('-ipc-invoke', new Event(), internal, channel, arguments); // webContents.emit('-ipc-invoke', new Event(), internal, channel, arguments);
EmitWithSender("-ipc-invoke", bindings_.dispatch_context(), EmitWithSender("-ipc-invoke", bindings_.dispatch_context(),
@ -1035,7 +1035,7 @@ void WebContents::Invoke(bool internal,
void WebContents::MessageSync(bool internal, void WebContents::MessageSync(bool internal,
const std::string& channel, const std::string& channel,
base::Value arguments, blink::CloneableMessage arguments,
MessageSyncCallback callback) { MessageSyncCallback callback) {
// webContents.emit('-ipc-message-sync', new Event(sender, message), internal, // webContents.emit('-ipc-message-sync', new Event(sender, message), internal,
// channel, arguments); // channel, arguments);
@ -1047,24 +1047,37 @@ void WebContents::MessageTo(bool internal,
bool send_to_all, bool send_to_all,
int32_t web_contents_id, int32_t web_contents_id,
const std::string& channel, const std::string& channel,
base::Value arguments) { blink::CloneableMessage arguments) {
auto* web_contents = mate::TrackableObject<WebContents>::FromWeakMapID( auto* web_contents = mate::TrackableObject<WebContents>::FromWeakMapID(
isolate(), web_contents_id); isolate(), web_contents_id);
if (web_contents) { if (web_contents) {
web_contents->SendIPCMessageWithSender(internal, send_to_all, channel, web_contents->SendIPCMessageWithSender(internal, send_to_all, channel,
base::ListValue(arguments.GetList()), std::move(arguments), ID());
ID());
} }
} }
void WebContents::MessageHost(const std::string& channel, void WebContents::MessageHost(const std::string& channel,
base::Value arguments) { blink::CloneableMessage arguments) {
// webContents.emit('ipc-message-host', new Event(), channel, args); // webContents.emit('ipc-message-host', new Event(), channel, args);
EmitWithSender("ipc-message-host", bindings_.dispatch_context(), EmitWithSender("ipc-message-host", bindings_.dispatch_context(),
base::nullopt, channel, std::move(arguments)); base::nullopt, channel, std::move(arguments));
} }
#if BUILDFLAG(ENABLE_REMOTE_MODULE)
void WebContents::DereferenceRemoteJSObject(const std::string& context_id,
int object_id,
int ref_count) {
base::ListValue args;
args.Append(context_id);
args.Append(object_id);
args.Append(ref_count);
EmitWithSender("-ipc-message", bindings_.dispatch_context(), base::nullopt,
/* internal */ true, "ELECTRON_BROWSER_DEREFERENCE",
std::move(args));
}
#endif
void WebContents::UpdateDraggableRegions( void WebContents::UpdateDraggableRegions(
std::vector<mojom::DraggableRegionPtr> regions) { std::vector<mojom::DraggableRegionPtr> regions) {
for (ExtendedWebContentsObserver& observer : observers_) for (ExtendedWebContentsObserver& observer : observers_)
@ -1986,14 +1999,21 @@ void WebContents::TabTraverse(bool reverse) {
bool WebContents::SendIPCMessage(bool internal, bool WebContents::SendIPCMessage(bool internal,
bool send_to_all, bool send_to_all,
const std::string& channel, const std::string& channel,
const base::ListValue& args) { v8::Local<v8::Value> args) {
return SendIPCMessageWithSender(internal, send_to_all, channel, args); blink::CloneableMessage message;
if (!mate::ConvertFromV8(isolate(), args, &message)) {
isolate()->ThrowException(v8::Exception::Error(
mate::StringToV8(isolate(), "Failed to serialize arguments")));
return false;
}
return SendIPCMessageWithSender(internal, send_to_all, channel,
std::move(message));
} }
bool WebContents::SendIPCMessageWithSender(bool internal, bool WebContents::SendIPCMessageWithSender(bool internal,
bool send_to_all, bool send_to_all,
const std::string& channel, const std::string& channel,
const base::ListValue& args, blink::CloneableMessage args,
int32_t sender_id) { int32_t sender_id) {
std::vector<content::RenderFrameHost*> target_hosts; std::vector<content::RenderFrameHost*> target_hosts;
if (!send_to_all) { if (!send_to_all) {
@ -2009,7 +2029,7 @@ bool WebContents::SendIPCMessageWithSender(bool internal,
mojom::ElectronRendererAssociatedPtr electron_ptr; mojom::ElectronRendererAssociatedPtr electron_ptr;
frame_host->GetRemoteAssociatedInterfaces()->GetInterface( frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
mojo::MakeRequest(&electron_ptr)); mojo::MakeRequest(&electron_ptr));
electron_ptr->Message(internal, false, channel, args.Clone(), sender_id); electron_ptr->Message(internal, false, channel, std::move(args), sender_id);
} }
return true; return true;
} }
@ -2018,7 +2038,13 @@ bool WebContents::SendIPCMessageToFrame(bool internal,
bool send_to_all, bool send_to_all,
int32_t frame_id, int32_t frame_id,
const std::string& channel, const std::string& channel,
const base::ListValue& args) { v8::Local<v8::Value> args) {
blink::CloneableMessage message;
if (!mate::ConvertFromV8(isolate(), args, &message)) {
isolate()->ThrowException(v8::Exception::Error(
mate::StringToV8(isolate(), "Failed to serialize arguments")));
return false;
}
auto frames = web_contents()->GetAllFrames(); auto frames = web_contents()->GetAllFrames();
auto iter = std::find_if(frames.begin(), frames.end(), [frame_id](auto* f) { auto iter = std::find_if(frames.begin(), frames.end(), [frame_id](auto* f) {
return f->GetRoutingID() == frame_id; return f->GetRoutingID() == frame_id;
@ -2031,7 +2057,7 @@ bool WebContents::SendIPCMessageToFrame(bool internal,
mojom::ElectronRendererAssociatedPtr electron_ptr; mojom::ElectronRendererAssociatedPtr electron_ptr;
(*iter)->GetRemoteAssociatedInterfaces()->GetInterface( (*iter)->GetRemoteAssociatedInterfaces()->GetInterface(
mojo::MakeRequest(&electron_ptr)); mojo::MakeRequest(&electron_ptr));
electron_ptr->Message(internal, send_to_all, channel, args.Clone(), electron_ptr->Message(internal, send_to_all, channel, std::move(message),
0 /* sender_id */); 0 /* sender_id */);
return true; return true;
} }

View file

@ -220,19 +220,19 @@ class WebContents : public mate::TrackableObject<WebContents>,
bool SendIPCMessage(bool internal, bool SendIPCMessage(bool internal,
bool send_to_all, bool send_to_all,
const std::string& channel, const std::string& channel,
const base::ListValue& args); v8::Local<v8::Value> args);
bool SendIPCMessageWithSender(bool internal, bool SendIPCMessageWithSender(bool internal,
bool send_to_all, bool send_to_all,
const std::string& channel, const std::string& channel,
const base::ListValue& args, blink::CloneableMessage args,
int32_t sender_id = 0); int32_t sender_id = 0);
bool SendIPCMessageToFrame(bool internal, bool SendIPCMessageToFrame(bool internal,
bool send_to_all, bool send_to_all,
int32_t frame_id, int32_t frame_id,
const std::string& channel, const std::string& channel,
const base::ListValue& args); v8::Local<v8::Value> args);
// Send WebInputEvent to the page. // Send WebInputEvent to the page.
void SendInputEvent(v8::Isolate* isolate, v8::Local<v8::Value> input_event); void SendInputEvent(v8::Isolate* isolate, v8::Local<v8::Value> input_event);
@ -491,21 +491,27 @@ class WebContents : public mate::TrackableObject<WebContents>,
// mojom::ElectronBrowser // mojom::ElectronBrowser
void Message(bool internal, void Message(bool internal,
const std::string& channel, const std::string& channel,
base::Value arguments) override; blink::CloneableMessage arguments) override;
void Invoke(bool internal, void Invoke(bool internal,
const std::string& channel, const std::string& channel,
base::Value arguments, blink::CloneableMessage arguments,
InvokeCallback callback) override; InvokeCallback callback) override;
void MessageSync(bool internal, void MessageSync(bool internal,
const std::string& channel, const std::string& channel,
base::Value arguments, blink::CloneableMessage arguments,
MessageSyncCallback callback) override; MessageSyncCallback callback) override;
void MessageTo(bool internal, void MessageTo(bool internal,
bool send_to_all, bool send_to_all,
int32_t web_contents_id, int32_t web_contents_id,
const std::string& channel, const std::string& channel,
base::Value arguments) override; blink::CloneableMessage arguments) override;
void MessageHost(const std::string& channel, base::Value arguments) override; void MessageHost(const std::string& channel,
blink::CloneableMessage arguments) override;
#if BUILDFLAG(ENABLE_REMOTE_MODULE)
void DereferenceRemoteJSObject(const std::string& context_id,
int object_id,
int ref_count) override;
#endif
void UpdateDraggableRegions( void UpdateDraggableRegions(
std::vector<mojom::DraggableRegionPtr> regions) override; std::vector<mojom::DraggableRegionPtr> regions) override;
void SetTemporaryZoomLevel(double level) override; void SetTemporaryZoomLevel(double level) override;

View file

@ -7,7 +7,7 @@
#include <utility> #include <utility>
#include "native_mate/object_template_builder_deprecated.h" #include "native_mate/object_template_builder_deprecated.h"
#include "shell/common/native_mate_converters/value_converter.h" #include "shell/common/native_mate_converters/blink_converter.h"
namespace mate { namespace mate {
@ -17,7 +17,7 @@ Event::Event(v8::Isolate* isolate) {
Event::~Event() = default; Event::~Event() = default;
void Event::SetCallback(base::Optional<MessageSyncCallback> callback) { void Event::SetCallback(base::Optional<InvokeCallback> callback) {
DCHECK(!callback_); DCHECK(!callback_);
callback_ = std::move(callback); callback_ = std::move(callback);
} }
@ -29,11 +29,16 @@ void Event::PreventDefault(v8::Isolate* isolate) {
.Check(); .Check();
} }
bool Event::SendReply(const base::Value& result) { bool Event::SendReply(v8::Isolate* isolate, v8::Local<v8::Value> result) {
if (!callback_) if (!callback_)
return false; return false;
std::move(*callback_).Run(result.Clone()); blink::CloneableMessage message;
if (!ConvertFromV8(isolate, result, &message)) {
return false;
}
std::move(*callback_).Run(std::move(message));
callback_.reset(); callback_.reset();
return true; return true;
} }

View file

@ -18,22 +18,21 @@ namespace mate {
class Event : public Wrappable<Event> { class Event : public Wrappable<Event> {
public: public:
using MessageSyncCallback = using InvokeCallback = electron::mojom::ElectronBrowser::InvokeCallback;
electron::mojom::ElectronBrowser::MessageSyncCallback;
static Handle<Event> Create(v8::Isolate* isolate); static Handle<Event> Create(v8::Isolate* isolate);
static void BuildPrototype(v8::Isolate* isolate, static void BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype); v8::Local<v8::FunctionTemplate> prototype);
// Pass the callback to be invoked. // Pass the callback to be invoked.
void SetCallback(base::Optional<MessageSyncCallback> callback); void SetCallback(base::Optional<InvokeCallback> callback);
// event.PreventDefault(). // event.PreventDefault().
void PreventDefault(v8::Isolate* isolate); void PreventDefault(v8::Isolate* isolate);
// event.sendReply(value), used for replying to synchronous messages and // event.sendReply(value), used for replying to synchronous messages and
// `invoke` calls. // `invoke` calls.
bool SendReply(const base::Value& result); bool SendReply(v8::Isolate* isolate, v8::Local<v8::Value> result);
protected: protected:
explicit Event(v8::Isolate* isolate); explicit Event(v8::Isolate* isolate);
@ -41,7 +40,7 @@ class Event : public Wrappable<Event> {
private: private:
// Replyer for the synchronous messages. // Replyer for the synchronous messages.
base::Optional<MessageSyncCallback> callback_; base::Optional<InvokeCallback> callback_;
DISALLOW_COPY_AND_ASSIGN(Event); DISALLOW_COPY_AND_ASSIGN(Event);
}; };

View file

@ -82,8 +82,7 @@ class EventEmitter : public Wrappable<T> {
bool EmitWithSender( bool EmitWithSender(
base::StringPiece name, base::StringPiece name,
content::RenderFrameHost* sender, content::RenderFrameHost* sender,
base::Optional<electron::mojom::ElectronBrowser::MessageSyncCallback> base::Optional<electron::mojom::ElectronBrowser::InvokeCallback> callback,
callback,
Args&&... args) { Args&&... args) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
v8::Locker locker(isolate()); v8::Locker locker(isolate());

View file

@ -1,4 +1,5 @@
import("//mojo/public/tools/bindings/mojom.gni") import("//mojo/public/tools/bindings/mojom.gni")
import("../../../buildflags/buildflags.gni")
mojom("mojo") { mojom("mojo") {
sources = [ sources = [
@ -7,6 +8,12 @@ mojom("mojo") {
public_deps = [ public_deps = [
"//mojo/public/mojom/base", "//mojo/public/mojom/base",
"//third_party/blink/public/mojom:mojom_core",
"//ui/gfx/geometry/mojom", "//ui/gfx/geometry/mojom",
] ]
enabled_features = []
if (enable_remote_module) {
enabled_features += [ "enable_remote_module" ]
}
} }

View file

@ -1,19 +1,26 @@
module electron.mojom; module electron.mojom;
import "mojo/public/mojom/base/values.mojom";
import "mojo/public/mojom/base/string16.mojom"; import "mojo/public/mojom/base/string16.mojom";
import "ui/gfx/geometry/mojom/geometry.mojom"; import "ui/gfx/geometry/mojom/geometry.mojom";
import "third_party/blink/public/mojom/messaging/cloneable_message.mojom";
interface ElectronRenderer { interface ElectronRenderer {
Message( Message(
bool internal, bool internal,
bool send_to_all, bool send_to_all,
string channel, string channel,
mojo_base.mojom.ListValue arguments, blink.mojom.CloneableMessage arguments,
int32 sender_id); int32 sender_id);
UpdateCrashpadPipeName(string pipe_name); UpdateCrashpadPipeName(string pipe_name);
// This is an API specific to the "remote" module, and will ultimately be
// replaced by generic IPC once WeakRef is generally available.
[EnableIf=enable_remote_module]
DereferenceRemoteJSCallback(
string context_id,
int32 object_id);
TakeHeapSnapshot(handle file) => (bool success); TakeHeapSnapshot(handle file) => (bool success);
}; };
@ -37,14 +44,14 @@ interface ElectronBrowser {
Message( Message(
bool internal, bool internal,
string channel, string channel,
mojo_base.mojom.ListValue arguments); blink.mojom.CloneableMessage arguments);
// Emits an event on |channel| from the ipcMain JavaScript object in the main // Emits an event on |channel| from the ipcMain JavaScript object in the main
// process, and returns the response. // process, and returns the response.
Invoke( Invoke(
bool internal, bool internal,
string channel, string channel,
mojo_base.mojom.ListValue arguments) => (mojo_base.mojom.Value result); blink.mojom.CloneableMessage arguments) => (blink.mojom.CloneableMessage result);
// Emits an event on |channel| from the ipcMain JavaScript object in the main // Emits an event on |channel| from the ipcMain JavaScript object in the main
// process, and waits synchronously for a response. // process, and waits synchronously for a response.
@ -55,7 +62,7 @@ interface ElectronBrowser {
MessageSync( MessageSync(
bool internal, bool internal,
string channel, string channel,
mojo_base.mojom.ListValue arguments) => (mojo_base.mojom.Value result); blink.mojom.CloneableMessage arguments) => (blink.mojom.CloneableMessage result);
// Emits an event from the |ipcRenderer| JavaScript object in the target // Emits an event from the |ipcRenderer| JavaScript object in the target
// WebContents's main frame, specified by |web_contents_id|. // WebContents's main frame, specified by |web_contents_id|.
@ -64,11 +71,19 @@ interface ElectronBrowser {
bool send_to_all, bool send_to_all,
int32 web_contents_id, int32 web_contents_id,
string channel, string channel,
mojo_base.mojom.ListValue arguments); blink.mojom.CloneableMessage arguments);
MessageHost( MessageHost(
string channel, string channel,
mojo_base.mojom.ListValue arguments); blink.mojom.CloneableMessage arguments);
// This is an API specific to the "remote" module, and will ultimately be
// replaced by generic IPC once WeakRef is generally available.
[EnableIf=enable_remote_module]
DereferenceRemoteJSObject(
string context_id,
int32 object_id,
int32 ref_count);
UpdateDraggableRegions( UpdateDraggableRegions(
array<DraggableRegion> regions); array<DraggableRegion> regions);

View file

@ -35,18 +35,12 @@ RemoteCallbackFreer::RemoteCallbackFreer(v8::Isolate* isolate,
RemoteCallbackFreer::~RemoteCallbackFreer() = default; RemoteCallbackFreer::~RemoteCallbackFreer() = default;
void RemoteCallbackFreer::RunDestructor() { 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(); auto* frame_host = web_contents()->GetMainFrame();
if (frame_host) { if (frame_host) {
mojom::ElectronRendererAssociatedPtr electron_ptr; mojom::ElectronRendererAssociatedPtr electron_ptr;
frame_host->GetRemoteAssociatedInterfaces()->GetInterface( frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
mojo::MakeRequest(&electron_ptr)); mojo::MakeRequest(&electron_ptr));
electron_ptr->Message(true /* internal */, false /* send_to_all */, channel, electron_ptr->DereferenceRemoteJSCallback(context_id_, object_id_);
args.Clone(), sender_id);
} }
Observe(nullptr); Observe(nullptr);

View file

@ -8,6 +8,8 @@
#include "base/values.h" #include "base/values.h"
#include "content/public/renderer/render_frame.h" #include "content/public/renderer/render_frame.h"
#include "electron/shell/common/api/api.mojom.h" #include "electron/shell/common/api/api.mojom.h"
#include "electron/shell/common/native_mate_converters/blink_converter.h"
#include "electron/shell/common/native_mate_converters/value_converter.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/web/web_local_frame.h" #include "third_party/blink/public/web/web_local_frame.h"
@ -80,17 +82,10 @@ void RemoteObjectFreer::RunDestructor() {
ref_mapper_.erase(objects_it); ref_mapper_.erase(objects_it);
} }
auto* channel = "ELECTRON_BROWSER_DEREFERENCE";
base::ListValue args;
args.AppendString(context_id_);
args.AppendInteger(object_id_);
args.AppendInteger(ref_count);
mojom::ElectronBrowserAssociatedPtr electron_ptr; mojom::ElectronBrowserAssociatedPtr electron_ptr;
render_frame->GetRemoteAssociatedInterfaces()->GetInterface( render_frame->GetRemoteAssociatedInterfaces()->GetInterface(
mojo::MakeRequest(&electron_ptr)); mojo::MakeRequest(&electron_ptr));
electron_ptr->Message(true, channel, args.Clone()); electron_ptr->DereferenceRemoteJSObject(context_id_, object_id_, ref_count);
} }
} // namespace electron } // namespace electron

View file

@ -0,0 +1,30 @@
// Copyright (c) 2019 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef SHELL_COMMON_GIN_CONVERTERS_BLINK_CONVERTER_GIN_ADAPTER_H_
#define SHELL_COMMON_GIN_CONVERTERS_BLINK_CONVERTER_GIN_ADAPTER_H_
#include "gin/converter.h"
#include "shell/common/native_mate_converters/blink_converter.h"
// TODO(zcbenz): Move the implementations from native_mate_converters to here.
namespace gin {
template <>
struct Converter<blink::CloneableMessage> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
blink::CloneableMessage* out) {
return mate::ConvertFromV8(isolate, val, out);
}
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
const blink::CloneableMessage& val) {
return mate::ConvertToV8(isolate, val);
}
};
} // namespace gin
#endif // SHELL_COMMON_GIN_CONVERTERS_BLINK_CONVERTER_GIN_ADAPTER_H_

View file

@ -6,14 +6,19 @@
#include <algorithm> #include <algorithm>
#include <string> #include <string>
#include <utility>
#include <vector> #include <vector>
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "content/public/browser/native_web_keyboard_event.h" #include "content/public/browser/native_web_keyboard_event.h"
#include "gin/converter.h" #include "gin/converter.h"
#include "mojo/public/cpp/base/values_mojom_traits.h"
#include "mojo/public/mojom/base/values.mojom.h"
#include "native_mate/dictionary.h" #include "native_mate/dictionary.h"
#include "shell/common/deprecate_util.h"
#include "shell/common/keyboard_util.h" #include "shell/common/keyboard_util.h"
#include "shell/common/native_mate_converters/value_converter.h"
#include "third_party/blink/public/platform/web_input_event.h" #include "third_party/blink/public/platform/web_input_event.h"
#include "third_party/blink/public/platform/web_mouse_event.h" #include "third_party/blink/public/platform/web_mouse_event.h"
#include "third_party/blink/public/platform/web_mouse_wheel_event.h" #include "third_party/blink/public/platform/web_mouse_wheel_event.h"
@ -527,4 +532,184 @@ bool Converter<network::mojom::ReferrerPolicy>::FromV8(
return true; return true;
} }
namespace {
constexpr uint8_t kNewSerializationTag = 0;
constexpr uint8_t kOldSerializationTag = 1;
class V8Serializer : public v8::ValueSerializer::Delegate {
public:
explicit V8Serializer(v8::Isolate* isolate,
bool use_old_serialization = false)
: isolate_(isolate),
serializer_(isolate, this),
use_old_serialization_(use_old_serialization) {}
~V8Serializer() override = default;
bool Serialize(v8::Local<v8::Value> value, blink::CloneableMessage* out) {
serializer_.WriteHeader();
if (use_old_serialization_) {
WriteTag(kOldSerializationTag);
if (!WriteBaseValue(value)) {
isolate_->ThrowException(
mate::StringToV8(isolate_, "An object could not be cloned."));
return false;
}
} else {
WriteTag(kNewSerializationTag);
bool wrote_value;
v8::TryCatch try_catch(isolate_);
if (!serializer_.WriteValue(isolate_->GetCurrentContext(), value)
.To(&wrote_value)) {
try_catch.Reset();
if (!V8Serializer(isolate_, true).Serialize(value, out)) {
try_catch.ReThrow();
return false;
}
return true;
}
DCHECK(wrote_value);
}
std::pair<uint8_t*, size_t> buffer = serializer_.Release();
DCHECK_EQ(buffer.first, data_.data());
out->encoded_message = base::make_span(buffer.first, buffer.second);
out->owned_encoded_message = std::move(data_);
return true;
}
bool WriteBaseValue(v8::Local<v8::Value> object) {
node::Environment* env = node::Environment::GetCurrent(isolate_);
if (env) {
electron::EmitDeprecationWarning(
env,
"Passing functions, DOM objects and other non-cloneable JavaScript "
"objects to IPC methods is deprecated and will throw an exception "
"beginning with Electron 9.",
"DeprecationWarning");
}
base::Value value;
if (!ConvertFromV8(isolate_, object, &value)) {
return false;
}
mojo::Message message = mojo_base::mojom::Value::SerializeAsMessage(&value);
serializer_.WriteUint32(message.data_num_bytes());
serializer_.WriteRawBytes(message.data(), message.data_num_bytes());
return true;
}
void WriteTag(uint8_t tag) { serializer_.WriteRawBytes(&tag, 1); }
// v8::ValueSerializer::Delegate
void* ReallocateBufferMemory(void* old_buffer,
size_t size,
size_t* actual_size) override {
DCHECK_EQ(old_buffer, data_.data());
data_.resize(size);
*actual_size = data_.capacity();
return data_.data();
}
void FreeBufferMemory(void* buffer) override {
DCHECK_EQ(buffer, data_.data());
data_ = {};
}
void ThrowDataCloneError(v8::Local<v8::String> message) override {
isolate_->ThrowException(v8::Exception::Error(message));
}
private:
v8::Isolate* isolate_;
std::vector<uint8_t> data_;
v8::ValueSerializer serializer_;
bool use_old_serialization_;
};
class V8Deserializer : public v8::ValueDeserializer::Delegate {
public:
V8Deserializer(v8::Isolate* isolate, const blink::CloneableMessage& message)
: isolate_(isolate),
deserializer_(isolate,
message.encoded_message.data(),
message.encoded_message.size(),
this) {}
v8::Local<v8::Value> Deserialize() {
v8::EscapableHandleScope scope(isolate_);
auto context = isolate_->GetCurrentContext();
bool read_header;
if (!deserializer_.ReadHeader(context).To(&read_header))
return v8::Null(isolate_);
DCHECK(read_header);
uint8_t tag;
if (!ReadTag(&tag))
return v8::Null(isolate_);
switch (tag) {
case kNewSerializationTag: {
v8::Local<v8::Value> value;
if (!deserializer_.ReadValue(context).ToLocal(&value)) {
return v8::Null(isolate_);
}
return scope.Escape(value);
}
case kOldSerializationTag: {
v8::Local<v8::Value> value;
if (!ReadBaseValue(&value)) {
return v8::Null(isolate_);
}
return scope.Escape(value);
}
default:
NOTREACHED() << "Invalid tag: " << tag;
return v8::Null(isolate_);
}
}
bool ReadTag(uint8_t* tag) {
const void* tag_bytes;
if (!deserializer_.ReadRawBytes(1, &tag_bytes))
return false;
*tag = *reinterpret_cast<const uint8_t*>(tag_bytes);
return true;
}
bool ReadBaseValue(v8::Local<v8::Value>* value) {
uint32_t length;
const void* data;
if (!deserializer_.ReadUint32(&length) ||
!deserializer_.ReadRawBytes(length, &data)) {
return false;
}
mojo::Message message(
base::make_span(reinterpret_cast<const uint8_t*>(data), length), {});
base::Value out;
if (!mojo_base::mojom::Value::DeserializeFromMessage(std::move(message),
&out)) {
return false;
}
*value = ConvertToV8(isolate_, out);
return true;
}
private:
v8::Isolate* isolate_;
v8::ValueDeserializer deserializer_;
};
} // namespace
v8::Local<v8::Value> Converter<blink::CloneableMessage>::ToV8(
v8::Isolate* isolate,
const blink::CloneableMessage& in) {
return V8Deserializer(isolate, in).Deserialize();
}
bool Converter<blink::CloneableMessage>::FromV8(v8::Isolate* isolate,
v8::Handle<v8::Value> val,
blink::CloneableMessage* out) {
return V8Serializer(isolate).Serialize(val, out);
}
} // namespace mate } // namespace mate

View file

@ -6,6 +6,7 @@
#define SHELL_COMMON_NATIVE_MATE_CONVERTERS_BLINK_CONVERTER_H_ #define SHELL_COMMON_NATIVE_MATE_CONVERTERS_BLINK_CONVERTER_H_
#include "native_mate/converter.h" #include "native_mate/converter.h"
#include "third_party/blink/public/common/messaging/cloneable_message.h"
#include "third_party/blink/public/platform/web_cache.h" #include "third_party/blink/public/platform/web_cache.h"
#include "third_party/blink/public/platform/web_input_event.h" #include "third_party/blink/public/platform/web_input_event.h"
#include "third_party/blink/public/web/web_context_menu_data.h" #include "third_party/blink/public/web/web_context_menu_data.h"
@ -131,6 +132,15 @@ struct Converter<network::mojom::ReferrerPolicy> {
network::mojom::ReferrerPolicy* out); network::mojom::ReferrerPolicy* out);
}; };
template <>
struct Converter<blink::CloneableMessage> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
const blink::CloneableMessage& in);
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
blink::CloneableMessage* out);
};
v8::Local<v8::Value> EditFlagsToV8(v8::Isolate* isolate, int editFlags); v8::Local<v8::Value> EditFlagsToV8(v8::Isolate* isolate, int editFlags);
v8::Local<v8::Value> MediaFlagsToV8(v8::Isolate* isolate, int mediaFlags); v8::Local<v8::Value> MediaFlagsToV8(v8::Isolate* isolate, int mediaFlags);

View file

@ -13,6 +13,7 @@
#include "gin/wrappable.h" #include "gin/wrappable.h"
#include "services/service_manager/public/cpp/interface_provider.h" #include "services/service_manager/public/cpp/interface_provider.h"
#include "shell/common/api/api.mojom.h" #include "shell/common/api/api.mojom.h"
#include "shell/common/gin_converters/blink_converter_gin_adapter.h"
#include "shell/common/gin_converters/value_converter_gin_adapter.h" #include "shell/common/gin_converters/value_converter_gin_adapter.h"
#include "shell/common/node_bindings.h" #include "shell/common/node_bindings.h"
#include "shell/common/node_includes.h" #include "shell/common/node_includes.h"
@ -69,45 +70,71 @@ class IPCRenderer : public gin::Wrappable<IPCRenderer> {
const char* GetTypeName() override { return "IPCRenderer"; } const char* GetTypeName() override { return "IPCRenderer"; }
private: private:
void Send(bool internal, void Send(v8::Isolate* isolate,
bool internal,
const std::string& channel, const std::string& channel,
const base::ListValue& arguments) { v8::Local<v8::Value> arguments) {
electron_browser_ptr_->get()->Message(internal, channel, arguments.Clone()); blink::CloneableMessage message;
if (!mate::ConvertFromV8(isolate, arguments, &message)) {
return;
}
electron_browser_ptr_->get()->Message(internal, channel,
std::move(message));
} }
v8::Local<v8::Promise> Invoke(v8::Isolate* isolate, v8::Local<v8::Promise> Invoke(v8::Isolate* isolate,
bool internal, bool internal,
const std::string& channel, const std::string& channel,
const base::Value& arguments) { v8::Local<v8::Value> arguments) {
electron::util::Promise<base::Value> p(isolate); blink::CloneableMessage message;
if (!mate::ConvertFromV8(isolate, arguments, &message)) {
return v8::Local<v8::Promise>();
}
electron::util::Promise<blink::CloneableMessage> p(isolate);
auto handle = p.GetHandle(); auto handle = p.GetHandle();
electron_browser_ptr_->get()->Invoke( electron_browser_ptr_->get()->Invoke(
internal, channel, arguments.Clone(), internal, channel, std::move(message),
base::BindOnce([](electron::util::Promise<base::Value> p, base::BindOnce(
base::Value result) { p.ResolveWithGin(result); }, [](electron::util::Promise<blink::CloneableMessage> p,
std::move(p))); blink::CloneableMessage result) { p.ResolveWithGin(result); },
std::move(p)));
return handle; return handle;
} }
void SendTo(bool internal, void SendTo(v8::Isolate* isolate,
bool internal,
bool send_to_all, bool send_to_all,
int32_t web_contents_id, int32_t web_contents_id,
const std::string& channel, const std::string& channel,
const base::ListValue& arguments) { v8::Local<v8::Value> arguments) {
blink::CloneableMessage message;
if (!mate::ConvertFromV8(isolate, arguments, &message)) {
return;
}
electron_browser_ptr_->get()->MessageTo( electron_browser_ptr_->get()->MessageTo(
internal, send_to_all, web_contents_id, channel, arguments.Clone()); internal, send_to_all, web_contents_id, channel, std::move(message));
} }
void SendToHost(const std::string& channel, void SendToHost(v8::Isolate* isolate,
const base::ListValue& arguments) { const std::string& channel,
electron_browser_ptr_->get()->MessageHost(channel, arguments.Clone()); v8::Local<v8::Value> arguments) {
blink::CloneableMessage message;
if (!mate::ConvertFromV8(isolate, arguments, &message)) {
return;
}
electron_browser_ptr_->get()->MessageHost(channel, std::move(message));
} }
base::Value SendSync(bool internal, blink::CloneableMessage SendSync(v8::Isolate* isolate,
const std::string& channel, bool internal,
const base::ListValue& arguments) { const std::string& channel,
v8::Local<v8::Value> arguments) {
blink::CloneableMessage message;
if (!mate::ConvertFromV8(isolate, arguments, &message)) {
return blink::CloneableMessage();
}
// We aren't using a true synchronous mojo call here. We're calling an // We aren't using a true synchronous mojo call here. We're calling an
// asynchronous method and blocking on the result. The reason we're doing // asynchronous method and blocking on the result. The reason we're doing
// this is a little complicated, so buckle up. // this is a little complicated, so buckle up.
@ -154,7 +181,7 @@ class IPCRenderer : public gin::Wrappable<IPCRenderer> {
// //
// Phew. If you got this far, here's a gold star: ⭐️ // Phew. If you got this far, here's a gold star: ⭐️
base::Value result; blink::CloneableMessage result;
// A task is posted to a worker thread to execute the request so that // A task is posted to a worker thread to execute the request so that
// this thread may block on a waitable event. It is safe to pass raw // this thread may block on a waitable event. It is safe to pass raw
@ -167,16 +194,16 @@ class IPCRenderer : public gin::Wrappable<IPCRenderer> {
base::Unretained(this), base::Unretained(this),
base::Unretained(&response_received_event), base::Unretained(&response_received_event),
base::Unretained(&result), internal, channel, base::Unretained(&result), internal, channel,
arguments.Clone())); std::move(message)));
response_received_event.Wait(); response_received_event.Wait();
return result; return result;
} }
void SendMessageSyncOnWorkerThread(base::WaitableEvent* event, void SendMessageSyncOnWorkerThread(base::WaitableEvent* event,
base::Value* result, blink::CloneableMessage* result,
bool internal, bool internal,
const std::string& channel, const std::string& channel,
base::Value arguments) { blink::CloneableMessage arguments) {
electron_browser_ptr_->get()->MessageSync( electron_browser_ptr_->get()->MessageSync(
internal, channel, std::move(arguments), internal, channel, std::move(arguments),
base::BindOnce(&IPCRenderer::ReturnSyncResponseToMainThread, base::BindOnce(&IPCRenderer::ReturnSyncResponseToMainThread,
@ -184,8 +211,8 @@ class IPCRenderer : public gin::Wrappable<IPCRenderer> {
} }
static void ReturnSyncResponseToMainThread(base::WaitableEvent* event, static void ReturnSyncResponseToMainThread(base::WaitableEvent* event,
base::Value* result, blink::CloneableMessage* result,
base::Value response) { blink::CloneableMessage response) {
*result = std::move(response); *result = std::move(response);
event->Signal(); event->Signal();
} }

View file

@ -13,6 +13,7 @@
#include "base/threading/thread_restrictions.h" #include "base/threading/thread_restrictions.h"
#include "mojo/public/cpp/system/platform_handle.h" #include "mojo/public/cpp/system/platform_handle.h"
#include "shell/common/atom_constants.h" #include "shell/common/atom_constants.h"
#include "shell/common/gin_converters/blink_converter_gin_adapter.h"
#include "shell/common/gin_converters/value_converter_gin_adapter.h" #include "shell/common/gin_converters/value_converter_gin_adapter.h"
#include "shell/common/heap_snapshot.h" #include "shell/common/heap_snapshot.h"
#include "shell/common/node_includes.h" #include "shell/common/node_includes.h"
@ -73,7 +74,7 @@ void InvokeIpcCallback(v8::Local<v8::Context> context,
void EmitIPCEvent(v8::Local<v8::Context> context, void EmitIPCEvent(v8::Local<v8::Context> context,
bool internal, bool internal,
const std::string& channel, const std::string& channel,
const std::vector<base::Value>& args, v8::Local<v8::Value> args,
int32_t sender_id) { int32_t sender_id) {
auto* isolate = context->GetIsolate(); auto* isolate = context->GetIsolate();
@ -84,7 +85,7 @@ void EmitIPCEvent(v8::Local<v8::Context> context,
std::vector<v8::Local<v8::Value>> argv = { std::vector<v8::Local<v8::Value>> argv = {
gin::ConvertToV8(isolate, internal), gin::ConvertToV8(isolate, channel), gin::ConvertToV8(isolate, internal), gin::ConvertToV8(isolate, channel),
gin::ConvertToV8(isolate, args), gin::ConvertToV8(isolate, sender_id)}; args, gin::ConvertToV8(isolate, sender_id)};
InvokeIpcCallback(context, "onMessage", argv); InvokeIpcCallback(context, "onMessage", argv);
} }
@ -128,7 +129,7 @@ void ElectronApiServiceImpl::OnConnectionError() {
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, blink::CloneableMessage arguments,
int32_t sender_id) { int32_t sender_id) {
// Don't handle browser messages before document element is created. // Don't handle browser messages before document element is created.
// //
@ -157,8 +158,11 @@ void ElectronApiServiceImpl::Message(bool internal,
v8::HandleScope handle_scope(isolate); v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = renderer_client_->GetContext(frame, isolate); v8::Local<v8::Context> context = renderer_client_->GetContext(frame, isolate);
v8::Context::Scope context_scope(context);
EmitIPCEvent(context, internal, channel, arguments.GetList(), sender_id); v8::Local<v8::Value> args = gin::ConvertToV8(isolate, arguments);
EmitIPCEvent(context, internal, channel, args, sender_id);
// Also send the message to all sub-frames. // Also send the message to all sub-frames.
// TODO(MarshallOfSound): Completely move this logic to the main process // TODO(MarshallOfSound): Completely move this logic to the main process
@ -168,12 +172,38 @@ void ElectronApiServiceImpl::Message(bool internal,
if (child->IsWebLocalFrame()) { if (child->IsWebLocalFrame()) {
v8::Local<v8::Context> child_context = v8::Local<v8::Context> child_context =
renderer_client_->GetContext(child->ToWebLocalFrame(), isolate); renderer_client_->GetContext(child->ToWebLocalFrame(), isolate);
EmitIPCEvent(child_context, internal, channel, arguments.GetList(), EmitIPCEvent(child_context, internal, channel, args, sender_id);
sender_id);
} }
} }
} }
#if BUILDFLAG(ENABLE_REMOTE_MODULE)
void ElectronApiServiceImpl::DereferenceRemoteJSCallback(
const std::string& context_id,
int32_t object_id) {
const auto* channel = "ELECTRON_RENDERER_RELEASE_CALLBACK";
if (!document_created_)
return;
blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
if (!frame)
return;
v8::Isolate* isolate = blink::MainThreadIsolate();
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Context> context = renderer_client_->GetContext(frame, isolate);
v8::Context::Scope context_scope(context);
base::ListValue args;
args.AppendString(context_id);
args.AppendInteger(object_id);
v8::Local<v8::Value> v8_args = gin::ConvertToV8(isolate, args);
EmitIPCEvent(context, true /* internal */, channel, v8_args,
0 /* sender_id */);
}
#endif
void ElectronApiServiceImpl::UpdateCrashpadPipeName( void ElectronApiServiceImpl::UpdateCrashpadPipeName(
const std::string& pipe_name) { const std::string& pipe_name) {
#if defined(OS_WIN) #if defined(OS_WIN)

View file

@ -10,6 +10,7 @@
#include "base/memory/weak_ptr.h" #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/buildflags/buildflags.h"
#include "electron/shell/common/api/api.mojom.h" #include "electron/shell/common/api/api.mojom.h"
#include "mojo/public/cpp/bindings/associated_binding.h" #include "mojo/public/cpp/bindings/associated_binding.h"
@ -28,8 +29,12 @@ class ElectronApiServiceImpl : public mojom::ElectronRenderer,
void Message(bool internal, void Message(bool internal,
bool send_to_all, bool send_to_all,
const std::string& channel, const std::string& channel,
base::Value arguments, blink::CloneableMessage arguments,
int32_t sender_id) override; int32_t sender_id) override;
#if BUILDFLAG(ENABLE_REMOTE_MODULE)
void DereferenceRemoteJSCallback(const std::string& context_id,
int32_t object_id) override;
#endif
void UpdateCrashpadPipeName(const std::string& pipe_name) override; void UpdateCrashpadPipeName(const std::string& pipe_name) override;
void TakeHeapSnapshot(mojo::ScopedHandle file, void TakeHeapSnapshot(mojo::ScopedHandle file,
TakeHeapSnapshotCallback callback) override; TakeHeapSnapshotCallback callback) override;

View file

@ -503,7 +503,7 @@ describe('app module', () => {
await w.loadURL('about:blank') await w.loadURL('about:blank')
const promise = emittedOnce(app, 'remote-get-current-window') const promise = emittedOnce(app, 'remote-get-current-window')
w.webContents.executeJavaScript(`require('electron').remote.getCurrentWindow()`) w.webContents.executeJavaScript(`{ require('electron').remote.getCurrentWindow() }`)
const [, webContents] = await promise const [, webContents] = await promise
expect(webContents).to.equal(w.webContents) expect(webContents).to.equal(w.webContents)
@ -519,7 +519,7 @@ describe('app module', () => {
await w.loadURL('about:blank') await w.loadURL('about:blank')
const promise = emittedOnce(app, 'remote-get-current-web-contents') const promise = emittedOnce(app, 'remote-get-current-web-contents')
w.webContents.executeJavaScript(`require('electron').remote.getCurrentWebContents()`) w.webContents.executeJavaScript(`{ require('electron').remote.getCurrentWebContents() }`)
const [, webContents] = await promise const [, webContents] = await promise
expect(webContents).to.equal(w.webContents) expect(webContents).to.equal(w.webContents)

View file

@ -1588,7 +1588,7 @@ describe('BrowserWindow module', () => {
}) })
w.loadFile(path.join(fixtures, 'api', 'preload.html')) w.loadFile(path.join(fixtures, 'api', 'preload.html'))
const [, test] = await emittedOnce(ipcMain, 'answer') const [, test] = await emittedOnce(ipcMain, 'answer')
expect(test.toString()).to.eql('buffer') expect(test).to.eql(Buffer.from('buffer'))
}) })
it('has synchronous access to all eventual window APIs', async () => { it('has synchronous access to all eventual window APIs', async () => {
const preload = path.join(fixtures, 'module', 'access-blink-apis.js') const preload = path.join(fixtures, 'module', 'access-blink-apis.js')
@ -1630,13 +1630,7 @@ describe('BrowserWindow module', () => {
const generateSpecs = (description: string, sandbox: boolean) => { const generateSpecs = (description: string, sandbox: boolean) => {
describe(description, () => { describe(description, () => {
it('loads the script before other scripts in window including normal preloads', function (done) { it('loads the script before other scripts in window including normal preloads', async () => {
ipcMain.once('vars', function (event, preload1, preload2, preload3) {
expect(preload1).to.equal('preload-1')
expect(preload2).to.equal('preload-1-2')
expect(preload3).to.be.null('preload 3')
done()
})
const w = new BrowserWindow({ const w = new BrowserWindow({
show: false, show: false,
webPreferences: { webPreferences: {
@ -1645,6 +1639,10 @@ describe('BrowserWindow module', () => {
} }
}) })
w.loadURL('about:blank') w.loadURL('about:blank')
const [, preload1, preload2, preload3] = await emittedOnce(ipcMain, 'vars')
expect(preload1).to.equal('preload-1')
expect(preload2).to.equal('preload-1-2')
expect(preload3).to.be.undefined('preload 3')
}) })
}) })
} }

View file

@ -16,7 +16,7 @@ ifdescribe(features.isDesktopCapturerEnabled() && !process.arch.includes('arm')
const getSources: typeof desktopCapturer.getSources = (options: SourcesOptions) => { const getSources: typeof desktopCapturer.getSources = (options: SourcesOptions) => {
return w.webContents.executeJavaScript(` return w.webContents.executeJavaScript(`
require('electron').desktopCapturer.getSources(${JSON.stringify(options)}) require('electron').desktopCapturer.getSources(${JSON.stringify(options)}).then(m => JSON.parse(JSON.stringify(m)))
`) `)
} }

View file

@ -31,14 +31,14 @@ describe('ipcRenderer module', () => {
expect(received).to.deep.equal(obj) expect(received).to.deep.equal(obj)
}) })
it('can send instances of Date as ISO strings', async () => { it('can send instances of Date as Dates', async () => {
const isoDate = new Date().toISOString() const isoDate = new Date().toISOString()
w.webContents.executeJavaScript(`{ w.webContents.executeJavaScript(`{
const { ipcRenderer } = require('electron') const { ipcRenderer } = require('electron')
ipcRenderer.send('message', new Date(${JSON.stringify(isoDate)})) ipcRenderer.send('message', new Date(${JSON.stringify(isoDate)}))
}`) }`)
const [, received] = await emittedOnce(ipcMain, 'message') const [, received] = await emittedOnce(ipcMain, 'message')
expect(received).to.equal(isoDate) expect(received.toISOString()).to.equal(isoDate)
}) })
it('can send instances of Buffer', async () => { it('can send instances of Buffer', async () => {
@ -48,10 +48,12 @@ describe('ipcRenderer module', () => {
ipcRenderer.send('message', Buffer.from(${JSON.stringify(data)})) ipcRenderer.send('message', Buffer.from(${JSON.stringify(data)}))
}`) }`)
const [, received] = await emittedOnce(ipcMain, 'message') const [, received] = await emittedOnce(ipcMain, 'message')
expect(received).to.be.an.instanceOf(Buffer) expect(received).to.be.an.instanceOf(Uint8Array)
expect(Buffer.from(data).equals(received)).to.be.true() expect(Buffer.from(data).equals(received)).to.be.true()
}) })
// TODO(nornagon): Change this test to expect an exception to be thrown in
// Electron 9.
it('can send objects with DOM class prototypes', async () => { it('can send objects with DOM class prototypes', async () => {
w.webContents.executeJavaScript(`{ w.webContents.executeJavaScript(`{
const { ipcRenderer } = require('electron') const { ipcRenderer } = require('electron')
@ -62,21 +64,20 @@ describe('ipcRenderer module', () => {
expect(value.hostname).to.equal('') expect(value.hostname).to.equal('')
}) })
it('does not crash on external objects (regression)', async () => { // TODO(nornagon): Change this test to expect an exception to be thrown in
// Electron 9.
it('does not crash when sending external objects', async () => {
w.webContents.executeJavaScript(`{ w.webContents.executeJavaScript(`{
const { ipcRenderer } = require('electron') const { ipcRenderer } = require('electron')
const http = require('http') const http = require('http')
const request = http.request({ port: 5000, hostname: '127.0.0.1', method: 'GET', path: '/' }) const request = http.request({ port: 5000, hostname: '127.0.0.1', method: 'GET', path: '/' })
const stream = request.agent.sockets['127.0.0.1:5000:'][0]._handle._externalStream const stream = request.agent.sockets['127.0.0.1:5000:'][0]._handle._externalStream
request.on('error', () => {})
ipcRenderer.send('message', request, stream) ipcRenderer.send('message', stream)
}`) }`)
const [, requestValue, externalStreamValue] = await emittedOnce(ipcMain, 'message') const [, externalStreamValue] = await emittedOnce(ipcMain, 'message')
expect(requestValue.method).to.equal('GET')
expect(requestValue.path).to.equal('/')
expect(externalStreamValue).to.be.null() expect(externalStreamValue).to.be.null()
}) })
@ -104,7 +105,7 @@ describe('ipcRenderer module', () => {
expect(childValue).to.deep.equal(child) expect(childValue).to.deep.equal(child)
}) })
it('inserts null for cyclic references', async () => { it('can handle cyclic references', async () => {
w.webContents.executeJavaScript(`{ w.webContents.executeJavaScript(`{
const { ipcRenderer } = require('electron') const { ipcRenderer } = require('electron')
const array = [5] const array = [5]
@ -117,10 +118,10 @@ describe('ipcRenderer module', () => {
const [, arrayValue, childValue] = await emittedOnce(ipcMain, 'message') const [, arrayValue, childValue] = await emittedOnce(ipcMain, 'message')
expect(arrayValue[0]).to.equal(5) expect(arrayValue[0]).to.equal(5)
expect(arrayValue[1]).to.be.null() expect(arrayValue[1]).to.equal(arrayValue)
expect(childValue.hello).to.equal('world') expect(childValue.hello).to.equal('world')
expect(childValue.child).to.be.null() expect(childValue.child).to.equal(childValue)
}) })
}) })
@ -182,9 +183,6 @@ describe('ipcRenderer module', () => {
generateSpecs('with contextIsolation', { contextIsolation: true }) generateSpecs('with contextIsolation', { contextIsolation: true })
generateSpecs('with contextIsolation + sandbox', { contextIsolation: true, sandbox: true }) generateSpecs('with contextIsolation + sandbox', { contextIsolation: true, sandbox: true })
}) })
/*
*/
describe('ipcRenderer.on', () => { describe('ipcRenderer.on', () => {
it('is not used for internals', async () => { it('is not used for internals', async () => {

View file

@ -84,19 +84,14 @@ describe('renderer nodeIntegrationInSubFrames', () => {
w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`)) w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`))
const details = await detailsPromise const details = await detailsPromise
const senders = details.map(event => event[0].sender) const senders = details.map(event => event[0].sender)
await new Promise(async resolve => { const isolatedGlobals = await Promise.all(senders.map(sender => sender.webContents.executeJavaScript('window.isolatedGlobal')))
let resultCount = 0 for (const result of isolatedGlobals) {
senders.forEach(async sender => { if (webPreferences.contextIsolation) {
const result = await sender.webContents.executeJavaScript('window.isolatedGlobal') expect(result).to.be.undefined()
if (webPreferences.contextIsolation) { } else {
expect(result).to.be.null() expect(result).to.equal(true)
} else { }
expect(result).to.equal(true) }
}
resultCount++
if (resultCount === senders.length) resolve()
})
})
}) })
}) })
} }

View file

@ -199,6 +199,7 @@ describe('webContents module', () => {
var iframe = document.createElement('iframe') var iframe = document.createElement('iframe')
iframe.src = '${serverUrl}/slow' iframe.src = '${serverUrl}/slow'
document.body.appendChild(iframe) document.body.appendChild(iframe)
null // don't return the iframe
`).then(() => { `).then(() => {
w.webContents.executeJavaScript('console.log(\'hello\')').then(() => { w.webContents.executeJavaScript('console.log(\'hello\')').then(() => {
done() done()
@ -215,15 +216,6 @@ describe('webContents module', () => {
}) })
w.loadURL(serverUrl) w.loadURL(serverUrl)
}) })
it('works with result objects that have DOM class prototypes', (done) => {
w.webContents.executeJavaScript('document.location').then(result => {
expect(result.origin).to.equal(serverUrl)
expect(result.protocol).to.equal('http:')
done()
})
w.loadURL(serverUrl)
})
}) })
}) })

View file

@ -81,7 +81,7 @@ describe('node feature', () => {
} }
function errorDataListener (data: Buffer) { function errorDataListener (data: Buffer) {
output += data output += data
if (output.trim().startsWith('Debugger listening on ws://')) { if (/^Debugger listening on ws:/m.test(output)) {
cleanup() cleanup()
done() done()
} }
@ -109,7 +109,7 @@ describe('node feature', () => {
} }
function errorDataListener (data: Buffer) { function errorDataListener (data: Buffer) {
output += data output += data
if (output.trim().startsWith('Debugger listening on ws://')) { if (/^Debugger listening on ws:/m.test(output)) {
expect(output.trim()).to.contain(':17364', 'should be listening on port 17364') expect(output.trim()).to.contain(':17364', 'should be listening on port 17364')
cleanup() cleanup()
done() done()
@ -203,4 +203,4 @@ describe('node feature', () => {
const result = childProcess.spawnSync(process.execPath, [path.resolve(fixtures, 'api', 'electron-main-module', 'app.asar')]) const result = childProcess.spawnSync(process.execPath, [path.resolve(fixtures, 'api', 'electron-main-module', 'app.asar')])
expect(result.status).to.equal(0) expect(result.status).to.equal(0)
}) })
}) })

View file

@ -545,4 +545,4 @@ describe('<webview> tag', function () {
}) })
}) })
}) })

View file

@ -241,14 +241,14 @@ ifdescribe(features.isRemoteModuleEnabled())('remote module', () => {
const print = path.join(fixtures, 'module', 'print_name.js') const print = path.join(fixtures, 'module', 'print_name.js')
const printName = remote.require(print) const printName = remote.require(print)
it('converts NaN to undefined', () => { it('preserves NaN', () => {
expect(printName.getNaN()).to.be.undefined() expect(printName.getNaN()).to.be.NaN()
expect(printName.echo(NaN)).to.be.undefined() expect(printName.echo(NaN)).to.be.NaN()
}) })
it('converts Infinity to undefined', () => { it('preserves Infinity', () => {
expect(printName.getInfinity()).to.be.undefined() expect(printName.getInfinity()).to.equal(Infinity)
expect(printName.echo(Infinity)).to.be.undefined() expect(printName.echo(Infinity)).to.equal(Infinity)
}) })
it('keeps its constructor name for objects', () => { it('keeps its constructor name for objects', () => {

View file

@ -28,7 +28,7 @@
creationTime: invoke(() => process.getCreationTime()), creationTime: invoke(() => process.getCreationTime()),
heapStatistics: invoke(() => process.getHeapStatistics()), heapStatistics: invoke(() => process.getHeapStatistics()),
blinkMemoryInfo: invoke(() => process.getBlinkMemoryInfo()), blinkMemoryInfo: invoke(() => process.getBlinkMemoryInfo()),
processMemoryInfo: invoke(() => process.getProcessMemoryInfo()), processMemoryInfo: invoke(() => process.getProcessMemoryInfo() ? {} : null),
systemMemoryInfo: invoke(() => process.getSystemMemoryInfo()), systemMemoryInfo: invoke(() => process.getSystemMemoryInfo()),
systemVersion: invoke(() => process.getSystemVersion()), systemVersion: invoke(() => process.getSystemVersion()),
cpuUsage: invoke(() => process.getCPUUsage()), cpuUsage: invoke(() => process.getCPUUsage()),

View file

@ -19,7 +19,7 @@
SendZoomLevel().then(() => { SendZoomLevel().then(() => {
if (!finalNavigation) { if (!finalNavigation) {
finalNavigation = true finalNavigation = true
view.executeJavaScript('window.location.hash=123', () => {}) view.executeJavaScript('window.location.hash=123')
} }
}) })
}) })

View file

@ -1068,7 +1068,7 @@ describe('<webview> tag', function () {
await loadWebView(webview, { src }) await loadWebView(webview, { src })
const data = await webview.printToPDF({}) const data = await webview.printToPDF({})
expect(data).to.be.an.instanceof(Buffer).that.is.not.empty() expect(data).to.be.an.instanceof(Uint8Array).that.is.not.empty()
}) })
}) })