diff --git a/docs/api/breaking-changes.md b/docs/api/breaking-changes.md index 57c0afedffb16..8e945438548d8 100644 --- a/docs/api/breaking-changes.md +++ b/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 diff --git a/docs/api/ipc-renderer.md b/docs/api/ipc-renderer.md index a6c99b20c35c7..2245761733ed3 100644 --- a/docs/api/ipc-renderer.md +++ b/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` - 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 diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 670657b777757..ec335b061f73c 100644 --- a/docs/api/web-contents.md +++ b/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 diff --git a/filenames.gni b/filenames.gni index 4435343b47a49..f244a471bcaf6 100644 --- a/filenames.gni +++ b/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", diff --git a/lib/browser/chrome-extension.js b/lib/browser/chrome-extension.js index dd0101e08f50e..c56e3f473f72b 100644 --- a/lib/browser/chrome-extension.js +++ b/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']) diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index c75076fa108a7..1720b9b6027b2 100644 --- a/lib/browser/guest-view-manager.js +++ b/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) diff --git a/lib/browser/guest-window-manager.js b/lib/browser/guest-window-manager.js index 7a17de8fbc1ad..66861a55c389a 100644 --- a/lib/browser/guest-window-manager.js +++ b/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 diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index a56c295e97e9a..c666a27cfda9e 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -114,7 +114,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 diff --git a/patches/chromium/.patches b/patches/chromium/.patches index 5327df58c070a..a49b99d941bc9 100644 --- a/patches/chromium/.patches +++ b/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 diff --git a/patches/chromium/export_fetchapi_mojo_traits_to_fix_component_build.patch b/patches/chromium/export_fetchapi_mojo_traits_to_fix_component_build.patch new file mode 100644 index 0000000000000..c763ea3cf05b0 --- /dev/null +++ b/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 +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> { + static bool IsNull(const scoped_refptr& r) { + return !r; +@@ -46,7 +47,7 @@ struct StructTraits +-struct StructTraits { + static const network::mojom::DataElementType& type( + const network::DataElement& element) { diff --git a/patches/chromium/gin_with_namespace.patch b/patches/chromium/gin_with_namespace.patch index 9a7101b0f33b7..4e54d0414a2a1 100644 --- a/patches/chromium/gin_with_namespace.patch +++ b/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::value, bool> TryConvertToV8( diff --git a/shell/browser/api/atom_api_web_contents.cc b/shell/browser/api/atom_api_web_contents.cc index 0ea92cced2062..ee30ec184b3ff 100644 --- a/shell/browser/api/atom_api_web_contents.cc +++ b/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::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 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 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 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 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; } diff --git a/shell/browser/api/atom_api_web_contents.h b/shell/browser/api/atom_api_web_contents.h index b45e9bddd16b9..db25c135cbac1 100644 --- a/shell/browser/api/atom_api_web_contents.h +++ b/shell/browser/api/atom_api_web_contents.h @@ -220,19 +220,19 @@ class WebContents : public mate::TrackableObject, bool SendIPCMessage(bool internal, bool send_to_all, const std::string& channel, - const base::ListValue& args); + v8::Local 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 args); // Send WebInputEvent to the page. void SendInputEvent(v8::Isolate* isolate, v8::Local input_event); @@ -491,21 +491,27 @@ class WebContents : public mate::TrackableObject, // 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 regions) override; void SetTemporaryZoomLevel(double level) override; diff --git a/shell/browser/api/event.cc b/shell/browser/api/event.cc index a4917ca1d2546..ac17b7bdc234d 100644 --- a/shell/browser/api/event.cc +++ b/shell/browser/api/event.cc @@ -7,7 +7,7 @@ #include #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 callback) { +void Event::SetCallback(base::Optional 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 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; } diff --git a/shell/browser/api/event.h b/shell/browser/api/event.h index 236e1e0cc1c0f..b60e23bb90900 100644 --- a/shell/browser/api/event.h +++ b/shell/browser/api/event.h @@ -18,22 +18,21 @@ namespace mate { class Event : public Wrappable { public: - using MessageSyncCallback = - electron::mojom::ElectronBrowser::MessageSyncCallback; + using InvokeCallback = electron::mojom::ElectronBrowser::InvokeCallback; static Handle Create(v8::Isolate* isolate); static void BuildPrototype(v8::Isolate* isolate, v8::Local prototype); // Pass the callback to be invoked. - void SetCallback(base::Optional callback); + void SetCallback(base::Optional 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 result); protected: explicit Event(v8::Isolate* isolate); @@ -41,7 +40,7 @@ class Event : public Wrappable { private: // Replyer for the synchronous messages. - base::Optional callback_; + base::Optional callback_; DISALLOW_COPY_AND_ASSIGN(Event); }; diff --git a/shell/browser/api/event_emitter.h b/shell/browser/api/event_emitter.h index a8880730d0780..8a5af5f6ce73c 100644 --- a/shell/browser/api/event_emitter.h +++ b/shell/browser/api/event_emitter.h @@ -82,8 +82,7 @@ class EventEmitter : public Wrappable { bool EmitWithSender( base::StringPiece name, content::RenderFrameHost* sender, - base::Optional - callback, + base::Optional callback, Args&&... args) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); v8::Locker locker(isolate()); diff --git a/shell/common/api/BUILD.gn b/shell/common/api/BUILD.gn index 81618157c039c..00a098013f245 100644 --- a/shell/common/api/BUILD.gn +++ b/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" ] + } } diff --git a/shell/common/api/api.mojom b/shell/common/api/api.mojom index 8bf04bfe92afd..9548c0d30a385 100644 --- a/shell/common/api/api.mojom +++ b/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 regions); diff --git a/shell/common/api/remote/remote_callback_freer.cc b/shell/common/api/remote/remote_callback_freer.cc index 8f468d9b8863e..2b977e399b36f 100644 --- a/shell/common/api/remote/remote_callback_freer.cc +++ b/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); diff --git a/shell/common/api/remote/remote_object_freer.cc b/shell/common/api/remote/remote_object_freer.cc index 68627b8386b19..43cdf18fe4dc6 100644 --- a/shell/common/api/remote/remote_object_freer.cc +++ b/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 diff --git a/shell/common/gin_converters/blink_converter_gin_adapter.h b/shell/common/gin_converters/blink_converter_gin_adapter.h new file mode 100644 index 0000000000000..4776cb4f26993 --- /dev/null +++ b/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 { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + blink::CloneableMessage* out) { + return mate::ConvertFromV8(isolate, val, out); + } + static v8::Local 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_ diff --git a/shell/common/native_mate_converters/blink_converter.cc b/shell/common/native_mate_converters/blink_converter.cc index 8b866b252b283..5d3fb35fb4862 100644 --- a/shell/common/native_mate_converters/blink_converter.cc +++ b/shell/common/native_mate_converters/blink_converter.cc @@ -6,14 +6,19 @@ #include #include +#include #include #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::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 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 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 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 message) override { + isolate_->ThrowException(v8::Exception::Error(message)); + } + + private: + v8::Isolate* isolate_; + std::vector 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 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 value; + if (!deserializer_.ReadValue(context).ToLocal(&value)) { + return v8::Null(isolate_); + } + return scope.Escape(value); + } + case kOldSerializationTag: { + v8::Local 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(tag_bytes); + return true; + } + + bool ReadBaseValue(v8::Local* 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(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 Converter::ToV8( + v8::Isolate* isolate, + const blink::CloneableMessage& in) { + return V8Deserializer(isolate, in).Deserialize(); +} + +bool Converter::FromV8(v8::Isolate* isolate, + v8::Handle val, + blink::CloneableMessage* out) { + return V8Serializer(isolate).Serialize(val, out); +} + } // namespace mate diff --git a/shell/common/native_mate_converters/blink_converter.h b/shell/common/native_mate_converters/blink_converter.h index 00b4c513f3aff..6613230bab997 100644 --- a/shell/common/native_mate_converters/blink_converter.h +++ b/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* out); }; +template <> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + const blink::CloneableMessage& in); + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + blink::CloneableMessage* out); +}; + v8::Local EditFlagsToV8(v8::Isolate* isolate, int editFlags); v8::Local MediaFlagsToV8(v8::Isolate* isolate, int mediaFlags); diff --git a/shell/renderer/api/atom_api_renderer_ipc.cc b/shell/renderer/api/atom_api_renderer_ipc.cc index 4577be16ff223..3ed4097e7a19e 100644 --- a/shell/renderer/api/atom_api_renderer_ipc.cc +++ b/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 { 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 arguments) { + blink::CloneableMessage message; + if (!mate::ConvertFromV8(isolate, arguments, &message)) { + return; + } + electron_browser_ptr_->get()->Message(internal, channel, + std::move(message)); } v8::Local Invoke(v8::Isolate* isolate, bool internal, const std::string& channel, - const base::Value& arguments) { - electron::util::Promise p(isolate); + v8::Local arguments) { + blink::CloneableMessage message; + if (!mate::ConvertFromV8(isolate, arguments, &message)) { + return v8::Local(); + } + electron::util::Promise p(isolate); auto handle = p.GetHandle(); electron_browser_ptr_->get()->Invoke( - internal, channel, arguments.Clone(), - base::BindOnce([](electron::util::Promise p, - base::Value result) { p.ResolveWithGin(result); }, - std::move(p))); + internal, channel, std::move(message), + base::BindOnce( + [](electron::util::Promise 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 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 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 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 { // // 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 { 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 { } static void ReturnSyncResponseToMainThread(base::WaitableEvent* event, - base::Value* result, - base::Value response) { + blink::CloneableMessage* result, + blink::CloneableMessage response) { *result = std::move(response); event->Signal(); } diff --git a/shell/renderer/electron_api_service_impl.cc b/shell/renderer/electron_api_service_impl.cc index ce7d572bf1699..08ee424bf3afa 100644 --- a/shell/renderer/electron_api_service_impl.cc +++ b/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 context, void EmitIPCEvent(v8::Local context, bool internal, const std::string& channel, - const std::vector& args, + v8::Local args, int32_t sender_id) { auto* isolate = context->GetIsolate(); @@ -84,7 +85,7 @@ void EmitIPCEvent(v8::Local context, std::vector> 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 context = renderer_client_->GetContext(frame, isolate); + v8::Context::Scope context_scope(context); + + v8::Local 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 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 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_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) diff --git a/shell/renderer/electron_api_service_impl.h b/shell/renderer/electron_api_service_impl.h index cbc1558508748..b6cb1dda3556a 100644 --- a/shell/renderer/electron_api_service_impl.h +++ b/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; diff --git a/spec-main/api-app-spec.ts b/spec-main/api-app-spec.ts index 7947572bea619..f152b3bbc20ec 100644 --- a/spec-main/api-app-spec.ts +++ b/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) diff --git a/spec-main/api-browser-window-spec.ts b/spec-main/api-browser-window-spec.ts index 00f069366e4c4..e4416676cd7b8 100644 --- a/spec-main/api-browser-window-spec.ts +++ b/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') }) }) } diff --git a/spec-main/api-desktop-capturer-spec.ts b/spec-main/api-desktop-capturer-spec.ts index b55eafec3e4f8..a043bda0ac0c3 100644 --- a/spec-main/api-desktop-capturer-spec.ts +++ b/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))) `) } diff --git a/spec-main/api-ipc-renderer-spec.ts b/spec-main/api-ipc-renderer-spec.ts index 01add860cd1e7..76476f4bdabb4 100644 --- a/spec-main/api-ipc-renderer-spec.ts +++ b/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 () => { diff --git a/spec-main/api-subframe-spec.ts b/spec-main/api-subframe-spec.ts index 8809f7f444cd3..3f76cadc702bd 100644 --- a/spec-main/api-subframe-spec.ts +++ b/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) + } + } }) }) } diff --git a/spec-main/api-web-contents-spec.ts b/spec-main/api-web-contents-spec.ts index 691e65da8cb6b..8e1c59dea96a2 100644 --- a/spec-main/api-web-contents-spec.ts +++ b/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) - }) }) }) diff --git a/spec-main/node-spec.ts b/spec-main/node-spec.ts index bb46159a9ba00..8d3af8e7623ec 100644 --- a/spec-main/node-spec.ts +++ b/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) }) -}) \ No newline at end of file +}) diff --git a/spec-main/webview-spec.ts b/spec-main/webview-spec.ts index d2f9816e168e2..d45db60b9c667 100644 --- a/spec-main/webview-spec.ts +++ b/spec-main/webview-spec.ts @@ -545,4 +545,4 @@ describe(' tag', function () { }) }) -}) \ No newline at end of file +}) diff --git a/spec/api-remote-spec.js b/spec/api-remote-spec.js index be96157defe1f..6a2e4935c93ad 100644 --- a/spec/api-remote-spec.js +++ b/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', () => { diff --git a/spec/fixtures/module/preload-sandbox.js b/spec/fixtures/module/preload-sandbox.js index e1fb9fbd7df85..d62e0e402abbc 100644 --- a/spec/fixtures/module/preload-sandbox.js +++ b/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()), diff --git a/spec/fixtures/pages/webview-in-page-navigate.html b/spec/fixtures/pages/webview-in-page-navigate.html index b50446e167436..7efdd0c3c74ad 100644 --- a/spec/fixtures/pages/webview-in-page-navigate.html +++ b/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') } }) }) diff --git a/spec/webview-spec.js b/spec/webview-spec.js index 5b83686fa84f4..4c0424164f952 100644 --- a/spec/webview-spec.js +++ b/spec/webview-spec.js @@ -1068,7 +1068,7 @@ describe(' 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() }) })