Browse Source

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
Jeremy Apthorp 5 years ago
parent
commit
2fad53e66b
38 changed files with 623 additions and 169 deletions
  1. 53 0
      docs/api/breaking-changes.md
  2. 33 11
      docs/api/ipc-renderer.md
  3. 19 5
      docs/api/web-contents.md
  4. 1 0
      filenames.gni
  5. 1 1
      lib/browser/chrome-extension.js
  6. 13 1
      lib/browser/guest-view-manager.js
  7. 1 2
      lib/browser/guest-window-manager.js
  8. 1 1
      lib/browser/rpc-server.js
  9. 1 0
      patches/chromium/.patches
  10. 36 0
      patches/chromium/export_fetchapi_mojo_traits_to_fix_component_build.patch
  11. 2 2
      patches/chromium/gin_with_namespace.patch
  12. 39 13
      shell/browser/api/atom_api_web_contents.cc
  13. 14 8
      shell/browser/api/atom_api_web_contents.h
  14. 9 4
      shell/browser/api/event.cc
  15. 4 5
      shell/browser/api/event.h
  16. 1 2
      shell/browser/api/event_emitter.h
  17. 7 0
      shell/common/api/BUILD.gn
  18. 22 7
      shell/common/api/api.mojom
  19. 1 7
      shell/common/api/remote/remote_callback_freer.cc
  20. 3 8
      shell/common/api/remote/remote_object_freer.cc
  21. 30 0
      shell/common/gin_converters/blink_converter_gin_adapter.h
  22. 185 0
      shell/common/native_mate_converters/blink_converter.cc
  23. 10 0
      shell/common/native_mate_converters/blink_converter.h
  24. 51 24
      shell/renderer/api/atom_api_renderer_ipc.cc
  25. 36 6
      shell/renderer/electron_api_service_impl.cc
  26. 6 1
      shell/renderer/electron_api_service_impl.h
  27. 2 2
      spec-main/api-app-spec.ts
  28. 6 8
      spec-main/api-browser-window-spec.ts
  29. 1 1
      spec-main/api-desktop-capturer-spec.ts
  30. 13 15
      spec-main/api-ipc-renderer-spec.ts
  31. 8 13
      spec-main/api-subframe-spec.ts
  32. 1 9
      spec-main/api-web-contents-spec.ts
  33. 3 3
      spec-main/node-spec.ts
  34. 1 1
      spec-main/webview-spec.ts
  35. 6 6
      spec/api-remote-spec.js
  36. 1 1
      spec/fixtures/module/preload-sandbox.js
  37. 1 1
      spec/fixtures/pages/webview-in-page-navigate.html
  38. 1 1
      spec/webview-spec.js

+ 53 - 0
docs/api/breaking-changes.md

@@ -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
 
+## 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)
 
 ### Node Headers URL

+ 33 - 11
docs/api/ipc-renderer.md

@@ -55,9 +55,15 @@ Removes all listeners, or those of the specified `channel`.
 * `channel` String
 * `...args` any[]
 
-Send a message to the main process asynchronously via `channel`, you can also
-send arbitrary arguments. Arguments will be serialized as JSON internally and
-hence no functions or prototype chain will be included.
+Send an asynchronous message to the main process via `channel`, along with
+arguments. Arguments will be serialized with the [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 main process handles it by listening for `channel` with the
 [`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.
 
-Send a message to the main process asynchronously via `channel` and expect an
-asynchronous result. Arguments will be serialized as JSON internally and
-hence no functions or prototype chain will be included.
+Send a message to the main process via `channel` and expect a result
+asynchronously. Arguments will be serialized with the [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 main process should listen for `channel` with
 [`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.
 
-Send a message to the main process synchronously via `channel`, you can also
-send arbitrary arguments. Arguments will be serialized in JSON internally and
-hence no functions or prototype chain will be included.
+Send a message to the main process via `channel` and expect a result
+synchronously. Arguments will be serialized with the [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 main process handles it by listening for `channel` with [`ipcMain`](ipc-main.md) module,
 and replies by setting `event.returnValue`.
 
-**Note:** Sending a synchronous message will block the whole renderer process,
-unless you know what you are doing you should never use it.
+> :warning: **WARNING**: Sending a synchronous message will block the whole
+> 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)`
 
@@ -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.
 
 [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

+ 19 - 5
docs/api/web-contents.md

@@ -1479,9 +1479,15 @@ Opens the developer tools for the service worker context.
 * `channel` String
 * `...args` any[]
 
-Send an asynchronous message to renderer process via `channel`, you can also
-send arbitrary arguments. Arguments will be serialized in JSON internally and
-hence no functions or prototype chain will be included.
+Send an asynchronous message to the renderer process via `channel`, along with
+arguments. Arguments will be serialized with the [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
 [`ipcRenderer`](ipc-renderer.md) module.
@@ -1522,8 +1528,14 @@ app.on('ready', () => {
 * `...args` any[]
 
 Send an asynchronous message to a specific frame in a renderer process via
-`channel`. Arguments will be serialized
-as JSON internally and as such no functions or prototype chains will be included.
+`channel`, along with arguments. Arguments will be serialized with the
+[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
 [`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
 [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

+ 1 - 0
filenames.gni

@@ -483,6 +483,7 @@ filenames = {
     "shell/common/gin_converters/net_converter.cc",
     "shell/common/gin_converters/net_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_helper/callback.cc",
     "shell/common/gin_helper/callback.h",

+ 1 - 1
lib/browser/chrome-extension.js

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

+ 13 - 1
lib/browser/guest-view-manager.js

@@ -23,7 +23,6 @@ const supportedWebViewEvents = [
   'devtools-opened',
   'devtools-closed',
   'devtools-focused',
-  'new-window',
   'will-navigate',
   'did-start-navigation',
   'did-navigate',
@@ -48,6 +47,13 @@ const supportedWebViewEvents = [
 const guestInstances = {}
 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.
 const createGuest = function (embedder, params) {
   if (webViewManager == null) {
@@ -114,6 +120,12 @@ const createGuest = function (embedder, params) {
     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.
   guest.on('ipc-message-host', function (_, channel, args) {
     sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE', channel, ...args)

+ 1 - 2
lib/browser/guest-window-manager.js

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

+ 1 - 1
lib/browser/rpc-server.js

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

+ 1 - 0
patches/chromium/.patches

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

+ 36 - 0
patches/chromium/export_fetchapi_mojo_traits_to_fix_component_build.patch

@@ -0,0 +1,36 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jeremy Apthorp <[email protected]>
+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) {

+ 2 - 2
patches/chromium/gin_with_namespace.patch

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

+ 39 - 13
shell/browser/api/atom_api_web_contents.cc

@@ -1017,7 +1017,7 @@ void WebContents::OnElectronBrowserConnectionError() {
 
 void WebContents::Message(bool internal,
                           const std::string& channel,
-                          base::Value arguments) {
+                          blink::CloneableMessage arguments) {
   // webContents.emit('-ipc-message', new Event(), internal, channel,
   // arguments);
   EmitWithSender("-ipc-message", bindings_.dispatch_context(), base::nullopt,
@@ -1026,7 +1026,7 @@ void WebContents::Message(bool internal,
 
 void WebContents::Invoke(bool internal,
                          const std::string& channel,
-                         base::Value arguments,
+                         blink::CloneableMessage arguments,
                          InvokeCallback callback) {
   // webContents.emit('-ipc-invoke', new Event(), internal, channel, arguments);
   EmitWithSender("-ipc-invoke", bindings_.dispatch_context(),
@@ -1035,7 +1035,7 @@ void WebContents::Invoke(bool internal,
 
 void WebContents::MessageSync(bool internal,
                               const std::string& channel,
-                              base::Value arguments,
+                              blink::CloneableMessage arguments,
                               MessageSyncCallback callback) {
   // webContents.emit('-ipc-message-sync', new Event(sender, message), internal,
   // channel, arguments);
@@ -1047,24 +1047,37 @@ void WebContents::MessageTo(bool internal,
                             bool send_to_all,
                             int32_t web_contents_id,
                             const std::string& channel,
-                            base::Value arguments) {
+                            blink::CloneableMessage arguments) {
   auto* web_contents = mate::TrackableObject<WebContents>::FromWeakMapID(
       isolate(), web_contents_id);
 
   if (web_contents) {
     web_contents->SendIPCMessageWithSender(internal, send_to_all, channel,
-                                           base::ListValue(arguments.GetList()),
-                                           ID());
+                                           std::move(arguments), ID());
   }
 }
 
 void WebContents::MessageHost(const std::string& channel,
-                              base::Value arguments) {
+                              blink::CloneableMessage arguments) {
   // webContents.emit('ipc-message-host', new Event(), channel, args);
   EmitWithSender("ipc-message-host", bindings_.dispatch_context(),
                  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(
     std::vector<mojom::DraggableRegionPtr> regions) {
   for (ExtendedWebContentsObserver& observer : observers_)
@@ -1986,14 +1999,21 @@ void WebContents::TabTraverse(bool reverse) {
 bool WebContents::SendIPCMessage(bool internal,
                                  bool send_to_all,
                                  const std::string& channel,
-                                 const base::ListValue& args) {
-  return SendIPCMessageWithSender(internal, send_to_all, channel, 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;
+  }
+  return SendIPCMessageWithSender(internal, send_to_all, channel,
+                                  std::move(message));
 }
 
 bool WebContents::SendIPCMessageWithSender(bool internal,
                                            bool send_to_all,
                                            const std::string& channel,
-                                           const base::ListValue& args,
+                                           blink::CloneableMessage args,
                                            int32_t sender_id) {
   std::vector<content::RenderFrameHost*> target_hosts;
   if (!send_to_all) {
@@ -2009,7 +2029,7 @@ bool WebContents::SendIPCMessageWithSender(bool internal,
     mojom::ElectronRendererAssociatedPtr electron_ptr;
     frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
         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;
 }
@@ -2018,7 +2038,13 @@ bool WebContents::SendIPCMessageToFrame(bool internal,
                                         bool send_to_all,
                                         int32_t frame_id,
                                         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 iter = std::find_if(frames.begin(), frames.end(), [frame_id](auto* f) {
     return f->GetRoutingID() == frame_id;
@@ -2031,7 +2057,7 @@ bool WebContents::SendIPCMessageToFrame(bool internal,
   mojom::ElectronRendererAssociatedPtr electron_ptr;
   (*iter)->GetRemoteAssociatedInterfaces()->GetInterface(
       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 */);
   return true;
 }

+ 14 - 8
shell/browser/api/atom_api_web_contents.h

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

+ 9 - 4
shell/browser/api/event.cc

@@ -7,7 +7,7 @@
 #include <utility>
 
 #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 {
 
@@ -17,7 +17,7 @@ Event::Event(v8::Isolate* isolate) {
 
 Event::~Event() = default;
 
-void Event::SetCallback(base::Optional<MessageSyncCallback> callback) {
+void Event::SetCallback(base::Optional<InvokeCallback> callback) {
   DCHECK(!callback_);
   callback_ = std::move(callback);
 }
@@ -29,11 +29,16 @@ void Event::PreventDefault(v8::Isolate* isolate) {
       .Check();
 }
 
-bool Event::SendReply(const base::Value& result) {
+bool Event::SendReply(v8::Isolate* isolate, v8::Local<v8::Value> result) {
   if (!callback_)
     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();
   return true;
 }

+ 4 - 5
shell/browser/api/event.h

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

+ 1 - 2
shell/browser/api/event_emitter.h

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

+ 7 - 0
shell/common/api/BUILD.gn

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

+ 22 - 7
shell/common/api/api.mojom

@@ -1,19 +1,26 @@
 module electron.mojom;
 
-import "mojo/public/mojom/base/values.mojom";
 import "mojo/public/mojom/base/string16.mojom";
 import "ui/gfx/geometry/mojom/geometry.mojom";
+import "third_party/blink/public/mojom/messaging/cloneable_message.mojom";
 
 interface ElectronRenderer {
   Message(
       bool internal,
       bool send_to_all,
       string channel,
-      mojo_base.mojom.ListValue arguments,
+      blink.mojom.CloneableMessage arguments,
       int32 sender_id);
 
   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);
 };
 
@@ -37,14 +44,14 @@ interface ElectronBrowser {
   Message(
       bool internal,
       string channel,
-      mojo_base.mojom.ListValue arguments);
+      blink.mojom.CloneableMessage arguments);
 
   // Emits an event on |channel| from the ipcMain JavaScript object in the main
   // process, and returns the response.
   Invoke(
       bool internal,
       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
   // process, and waits synchronously for a response.
@@ -55,7 +62,7 @@ interface ElectronBrowser {
   MessageSync(
     bool internal,
     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
   // WebContents's main frame, specified by |web_contents_id|.
@@ -64,11 +71,19 @@ interface ElectronBrowser {
     bool send_to_all,
     int32 web_contents_id,
     string channel,
-    mojo_base.mojom.ListValue arguments);
+    blink.mojom.CloneableMessage arguments);
 
   MessageHost(
     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(
     array<DraggableRegion> regions);

+ 1 - 7
shell/common/api/remote/remote_callback_freer.cc

@@ -35,18 +35,12 @@ RemoteCallbackFreer::RemoteCallbackFreer(v8::Isolate* isolate,
 RemoteCallbackFreer::~RemoteCallbackFreer() = default;
 
 void RemoteCallbackFreer::RunDestructor() {
-  auto* channel = "ELECTRON_RENDERER_RELEASE_CALLBACK";
-  base::ListValue args;
-  int32_t sender_id = 0;
-  args.AppendString(context_id_);
-  args.AppendInteger(object_id_);
   auto* frame_host = web_contents()->GetMainFrame();
   if (frame_host) {
     mojom::ElectronRendererAssociatedPtr electron_ptr;
     frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
         mojo::MakeRequest(&electron_ptr));
-    electron_ptr->Message(true /* internal */, false /* send_to_all */, channel,
-                          args.Clone(), sender_id);
+    electron_ptr->DereferenceRemoteJSCallback(context_id_, object_id_);
   }
 
   Observe(nullptr);

+ 3 - 8
shell/common/api/remote/remote_object_freer.cc

@@ -8,6 +8,8 @@
 #include "base/values.h"
 #include "content/public/renderer/render_frame.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/web/web_local_frame.h"
 
@@ -80,17 +82,10 @@ void RemoteObjectFreer::RunDestructor() {
       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;
   render_frame->GetRemoteAssociatedInterfaces()->GetInterface(
       mojo::MakeRequest(&electron_ptr));
-  electron_ptr->Message(true, channel, args.Clone());
+  electron_ptr->DereferenceRemoteJSObject(context_id_, object_id_, ref_count);
 }
 
 }  // namespace electron

+ 30 - 0
shell/common/gin_converters/blink_converter_gin_adapter.h

@@ -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_

+ 185 - 0
shell/common/native_mate_converters/blink_converter.cc

@@ -6,14 +6,19 @@
 
 #include <algorithm>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "content/public/browser/native_web_keyboard_event.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 "shell/common/deprecate_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_mouse_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;
 }
 
+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

+ 10 - 0
shell/common/native_mate_converters/blink_converter.h

@@ -6,6 +6,7 @@
 #define SHELL_COMMON_NATIVE_MATE_CONVERTERS_BLINK_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_input_event.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);
 };
 
+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> MediaFlagsToV8(v8::Isolate* isolate, int mediaFlags);
 

+ 51 - 24
shell/renderer/api/atom_api_renderer_ipc.cc

@@ -13,6 +13,7 @@
 #include "gin/wrappable.h"
 #include "services/service_manager/public/cpp/interface_provider.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/node_bindings.h"
 #include "shell/common/node_includes.h"
@@ -69,45 +70,71 @@ class IPCRenderer : public gin::Wrappable<IPCRenderer> {
   const char* GetTypeName() override { return "IPCRenderer"; }
 
  private:
-  void Send(bool internal,
+  void Send(v8::Isolate* isolate,
+            bool internal,
             const std::string& channel,
-            const base::ListValue& arguments) {
-    electron_browser_ptr_->get()->Message(internal, channel, arguments.Clone());
+            v8::Local<v8::Value> arguments) {
+    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,
                                 bool internal,
                                 const std::string& channel,
-                                const base::Value& arguments) {
-    electron::util::Promise<base::Value> p(isolate);
+                                v8::Local<v8::Value> arguments) {
+    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();
 
     electron_browser_ptr_->get()->Invoke(
-        internal, channel, arguments.Clone(),
-        base::BindOnce([](electron::util::Promise<base::Value> p,
-                          base::Value result) { p.ResolveWithGin(result); },
-                       std::move(p)));
+        internal, channel, std::move(message),
+        base::BindOnce(
+            [](electron::util::Promise<blink::CloneableMessage> p,
+               blink::CloneableMessage result) { p.ResolveWithGin(result); },
+            std::move(p)));
 
     return handle;
   }
 
-  void SendTo(bool internal,
+  void SendTo(v8::Isolate* isolate,
+              bool internal,
               bool send_to_all,
               int32_t web_contents_id,
               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(
-        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,
-                  const base::ListValue& arguments) {
-    electron_browser_ptr_->get()->MessageHost(channel, arguments.Clone());
+  void SendToHost(v8::Isolate* isolate,
+                  const std::string& channel,
+                  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,
-                       const std::string& channel,
-                       const base::ListValue& arguments) {
+  blink::CloneableMessage SendSync(v8::Isolate* isolate,
+                                   bool internal,
+                                   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
     // asynchronous method and blocking on the result. The reason we're doing
     // 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: ⭐️
 
-    base::Value result;
+    blink::CloneableMessage result;
 
     // 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
@@ -167,16 +194,16 @@ class IPCRenderer : public gin::Wrappable<IPCRenderer> {
                                   base::Unretained(this),
                                   base::Unretained(&response_received_event),
                                   base::Unretained(&result), internal, channel,
-                                  arguments.Clone()));
+                                  std::move(message)));
     response_received_event.Wait();
     return result;
   }
 
   void SendMessageSyncOnWorkerThread(base::WaitableEvent* event,
-                                     base::Value* result,
+                                     blink::CloneableMessage* result,
                                      bool internal,
                                      const std::string& channel,
-                                     base::Value arguments) {
+                                     blink::CloneableMessage arguments) {
     electron_browser_ptr_->get()->MessageSync(
         internal, channel, std::move(arguments),
         base::BindOnce(&IPCRenderer::ReturnSyncResponseToMainThread,
@@ -184,8 +211,8 @@ class IPCRenderer : public gin::Wrappable<IPCRenderer> {
   }
 
   static void ReturnSyncResponseToMainThread(base::WaitableEvent* event,
-                                             base::Value* result,
-                                             base::Value response) {
+                                             blink::CloneableMessage* result,
+                                             blink::CloneableMessage response) {
     *result = std::move(response);
     event->Signal();
   }

+ 36 - 6
shell/renderer/electron_api_service_impl.cc

@@ -13,6 +13,7 @@
 #include "base/threading/thread_restrictions.h"
 #include "mojo/public/cpp/system/platform_handle.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/heap_snapshot.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,
                   bool internal,
                   const std::string& channel,
-                  const std::vector<base::Value>& args,
+                  v8::Local<v8::Value> args,
                   int32_t sender_id) {
   auto* isolate = context->GetIsolate();
 
@@ -84,7 +85,7 @@ void EmitIPCEvent(v8::Local<v8::Context> context,
 
   std::vector<v8::Local<v8::Value>> argv = {
       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);
 }
@@ -128,7 +129,7 @@ void ElectronApiServiceImpl::OnConnectionError() {
 void ElectronApiServiceImpl::Message(bool internal,
                                      bool send_to_all,
                                      const std::string& channel,
-                                     base::Value arguments,
+                                     blink::CloneableMessage arguments,
                                      int32_t sender_id) {
   // 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::Local<v8::Context> context = renderer_client_->GetContext(frame, isolate);
+  v8::Context::Scope context_scope(context);
+
+  v8::Local<v8::Value> args = gin::ConvertToV8(isolate, arguments);
 
-  EmitIPCEvent(context, internal, channel, arguments.GetList(), sender_id);
+  EmitIPCEvent(context, internal, channel, args, sender_id);
 
   // Also send the message to all sub-frames.
   // TODO(MarshallOfSound): Completely move this logic to the main process
@@ -168,12 +172,38 @@ void ElectronApiServiceImpl::Message(bool internal,
       if (child->IsWebLocalFrame()) {
         v8::Local<v8::Context> child_context =
             renderer_client_->GetContext(child->ToWebLocalFrame(), isolate);
-        EmitIPCEvent(child_context, internal, channel, arguments.GetList(),
-                     sender_id);
+        EmitIPCEvent(child_context, internal, channel, args, 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(
     const std::string& pipe_name) {
 #if defined(OS_WIN)

+ 6 - 1
shell/renderer/electron_api_service_impl.h

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

+ 2 - 2
spec-main/api-app-spec.ts

@@ -503,7 +503,7 @@ describe('app module', () => {
       await w.loadURL('about:blank')
 
       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
       expect(webContents).to.equal(w.webContents)
@@ -519,7 +519,7 @@ describe('app module', () => {
       await w.loadURL('about:blank')
 
       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
       expect(webContents).to.equal(w.webContents)

+ 6 - 8
spec-main/api-browser-window-spec.ts

@@ -1588,7 +1588,7 @@ describe('BrowserWindow module', () => {
         })
         w.loadFile(path.join(fixtures, 'api', 'preload.html'))
         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 () => {
         const preload = path.join(fixtures, 'module', 'access-blink-apis.js')
@@ -1630,13 +1630,7 @@ describe('BrowserWindow module', () => {
 
       const generateSpecs = (description: string, sandbox: boolean) => {
         describe(description, () => {
-          it('loads the script before other scripts in window including normal preloads', function (done) {
-            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()
-            })
+          it('loads the script before other scripts in window including normal preloads', async () => {
             const w = new BrowserWindow({
               show: false,
               webPreferences: {
@@ -1645,6 +1639,10 @@ describe('BrowserWindow module', () => {
               }
             })
             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')
           })
         })
       }

+ 1 - 1
spec-main/api-desktop-capturer-spec.ts

@@ -16,7 +16,7 @@ ifdescribe(features.isDesktopCapturerEnabled() && !process.arch.includes('arm')
 
   const getSources: typeof desktopCapturer.getSources = (options: SourcesOptions) => {
     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)))
     `)
   }
 

+ 13 - 15
spec-main/api-ipc-renderer-spec.ts

@@ -31,14 +31,14 @@ describe('ipcRenderer module', () => {
       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()
       w.webContents.executeJavaScript(`{
         const { ipcRenderer } = require('electron')
         ipcRenderer.send('message', new Date(${JSON.stringify(isoDate)}))
       }`)
       const [, received] = await emittedOnce(ipcMain, 'message')
-      expect(received).to.equal(isoDate)
+      expect(received.toISOString()).to.equal(isoDate)
     })
 
     it('can send instances of Buffer', async () => {
@@ -48,10 +48,12 @@ describe('ipcRenderer module', () => {
         ipcRenderer.send('message', Buffer.from(${JSON.stringify(data)}))
       }`)
       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()
     })
 
+    // TODO(nornagon): Change this test to expect an exception to be thrown in
+    // Electron 9.
     it('can send objects with DOM class prototypes', async () => {
       w.webContents.executeJavaScript(`{
         const { ipcRenderer } = require('electron')
@@ -62,21 +64,20 @@ describe('ipcRenderer module', () => {
       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(`{
         const { ipcRenderer } = require('electron')
         const http = require('http')
 
         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
-        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()
     })
 
@@ -104,7 +105,7 @@ describe('ipcRenderer module', () => {
       expect(childValue).to.deep.equal(child)
     })
 
-    it('inserts null for cyclic references', async () => {
+    it('can handle cyclic references', async () => {
       w.webContents.executeJavaScript(`{
         const { ipcRenderer } = require('electron')
         const array = [5]
@@ -117,10 +118,10 @@ describe('ipcRenderer module', () => {
 
       const [, arrayValue, childValue] = await emittedOnce(ipcMain, 'message')
       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.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 + sandbox', { contextIsolation: true, sandbox: true })
   })
-  /*
-
-  */
 
   describe('ipcRenderer.on', () => {
     it('is not used for internals', async () => {

+ 8 - 13
spec-main/api-subframe-spec.ts

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

+ 1 - 9
spec-main/api-web-contents-spec.ts

@@ -199,6 +199,7 @@ describe('webContents module', () => {
             var iframe = document.createElement('iframe')
             iframe.src = '${serverUrl}/slow'
             document.body.appendChild(iframe)
+            null // don't return the iframe
           `).then(() => {
             w.webContents.executeJavaScript('console.log(\'hello\')').then(() => {
               done()
@@ -215,15 +216,6 @@ describe('webContents module', () => {
         })
         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)
-      })
     })
   })
 

+ 3 - 3
spec-main/node-spec.ts

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

+ 1 - 1
spec-main/webview-spec.ts

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

+ 6 - 6
spec/api-remote-spec.js

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

+ 1 - 1
spec/fixtures/module/preload-sandbox.js

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

+ 1 - 1
spec/fixtures/pages/webview-in-page-navigate.html

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

+ 1 - 1
spec/webview-spec.js

@@ -1068,7 +1068,7 @@ describe('<webview> tag', function () {
       await loadWebView(webview, { src })
 
       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()
     })
   })