mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
Compare commits
19 Commits
35-x-y
...
v21.0.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76e8536ca8 | ||
|
|
b075434447 | ||
|
|
9673d29ebe | ||
|
|
5e6d152231 | ||
|
|
b31a4b4394 | ||
|
|
28ca18a912 | ||
|
|
8caea9006a | ||
|
|
855536bcb9 | ||
|
|
1978a0b5fe | ||
|
|
fe70152b43 | ||
|
|
9b38a25206 | ||
|
|
a66d3bf206 | ||
|
|
752b10f16f | ||
|
|
5803a67963 | ||
|
|
40058af821 | ||
|
|
64b40ea69c | ||
|
|
7c6e16975f | ||
|
|
c9e7f33f7e | ||
|
|
98aa0ef090 |
@@ -1 +1 @@
|
||||
21.0.0-nightly.20220803
|
||||
21.0.0-alpha.5
|
||||
@@ -1049,7 +1049,7 @@ is emitted.
|
||||
|
||||
#### `ses.getStoragePath()`
|
||||
|
||||
A `string | null` indicating the absolute file system path where data for this
|
||||
Returns `string | null` - The absolute file system path where data for this
|
||||
session is persisted on disk. For in memory sessions this returns `null`.
|
||||
|
||||
### Instance Properties
|
||||
|
||||
@@ -25,15 +25,20 @@ app.whenReady().then(() => {
|
||||
})
|
||||
```
|
||||
|
||||
__Platform limitations:__
|
||||
__Platform Considerations__
|
||||
|
||||
If you want to keep exact same behaviors on all platforms, you should not
|
||||
rely on the `click` event; instead, always attach a context menu to the tray icon.
|
||||
|
||||
__Linux__
|
||||
|
||||
* On Linux the app indicator will be used if it is supported, otherwise
|
||||
`GtkStatusIcon` will be used instead.
|
||||
* On Linux distributions that only have app indicator support, you have to
|
||||
install `libappindicator1` to make the tray icon work.
|
||||
* The app indicator will be used if it is supported, otherwise
|
||||
`GtkStatusIcon` will be used instead.
|
||||
* App indicator will only be shown when it has a context menu.
|
||||
* When app indicator is used on Linux, the `click` event is ignored.
|
||||
* On Linux in order for changes made to individual `MenuItem`s to take effect,
|
||||
* The `click` event is ignored when using the app indicator.
|
||||
* In order for changes made to individual `MenuItem`s to take effect,
|
||||
you have to call `setContextMenu` again. For example:
|
||||
|
||||
```javascript
|
||||
@@ -55,10 +60,16 @@ app.whenReady().then(() => {
|
||||
})
|
||||
```
|
||||
|
||||
* On Windows it is recommended to use `ICO` icons to get best visual effects.
|
||||
__MacOS__
|
||||
|
||||
If you want to keep exact same behaviors on all platforms, you should not
|
||||
rely on the `click` event and always attach a context menu to the tray icon.
|
||||
* Icons passed to the Tray constructor should be [Template Images](native-image.md#template-image).
|
||||
* To make sure your icon isn't grainy on retina monitors, be sure your `@2x` image is 144dpi.
|
||||
* If you are bundling your application (e.g., with webpack for development), be sure that the file names are not being mangled or hashed. The filename needs to end in Template, and the `@2x` image needs to have the same filename as the standard image, or MacOS will not magically invert your image's colors or use the high density image.
|
||||
* 16x16 (72dpi) and 32x32@2x (144dpi) work well for most icons.
|
||||
|
||||
__Windows__
|
||||
|
||||
* It is recommended to use `ICO` icons to get best visual effects.
|
||||
|
||||
### `new Tray(image, [guid])`
|
||||
|
||||
|
||||
@@ -862,6 +862,8 @@ Returns:
|
||||
|
||||
Emitted when the renderer process sends an asynchronous message via `ipcRenderer.send()`.
|
||||
|
||||
See also [`webContents.ipc`](#contentsipc-readonly), which provides an [`IpcMain`](ipc-main.md)-like interface for responding to IPC messages specifically from this WebContents.
|
||||
|
||||
#### Event: 'ipc-message-sync'
|
||||
|
||||
Returns:
|
||||
@@ -872,6 +874,8 @@ Returns:
|
||||
|
||||
Emitted when the renderer process sends a synchronous message via `ipcRenderer.sendSync()`.
|
||||
|
||||
See also [`webContents.ipc`](#contentsipc-readonly), which provides an [`IpcMain`](ipc-main.md)-like interface for responding to IPC messages specifically from this WebContents.
|
||||
|
||||
#### Event: 'preferred-size-changed'
|
||||
|
||||
Returns:
|
||||
@@ -1985,6 +1989,35 @@ This corresponds to the [animationPolicy][] accessibility feature in Chromium.
|
||||
|
||||
### Instance Properties
|
||||
|
||||
#### `contents.ipc` _Readonly_
|
||||
|
||||
An [`IpcMain`](ipc-main.md) scoped to just IPC messages sent from this
|
||||
WebContents.
|
||||
|
||||
IPC messages sent with `ipcRenderer.send`, `ipcRenderer.sendSync` or
|
||||
`ipcRenderer.postMessage` will be delivered in the following order:
|
||||
|
||||
1. `contents.on('ipc-message')`
|
||||
2. `contents.mainFrame.on(channel)`
|
||||
3. `contents.ipc.on(channel)`
|
||||
4. `ipcMain.on(channel)`
|
||||
|
||||
Handlers registered with `invoke` will be checked in the following order. The
|
||||
first one that is defined will be called, the rest will be ignored.
|
||||
|
||||
1. `contents.mainFrame.handle(channel)`
|
||||
2. `contents.handle(channel)`
|
||||
3. `ipcMain.handle(channel)`
|
||||
|
||||
A handler or event listener registered on the WebContents will receive IPC
|
||||
messages sent from any frame, including child frames. In most cases, only the
|
||||
main frame can send IPC messages. However, if the `nodeIntegrationInSubFrames`
|
||||
option is enabled, it is possible for child frames to send IPC messages also.
|
||||
In that case, handlers should check the `senderFrame` property of the IPC event
|
||||
to ensure that the message is coming from the expected frame. Alternatively,
|
||||
register handlers on the appropriate frame directly using the
|
||||
[`WebFrameMain.ipc`](web-frame-main.md#frameipc-readonly) interface.
|
||||
|
||||
#### `contents.audioMuted`
|
||||
|
||||
A `boolean` property that determines whether this page is muted.
|
||||
|
||||
@@ -140,6 +140,31 @@ ipcRenderer.on('port', (e, msg) => {
|
||||
|
||||
### Instance Properties
|
||||
|
||||
#### `frame.ipc` _Readonly_
|
||||
|
||||
An [`IpcMain`](ipc-main.md) instance scoped to the frame.
|
||||
|
||||
IPC messages sent with `ipcRenderer.send`, `ipcRenderer.sendSync` or
|
||||
`ipcRenderer.postMessage` will be delivered in the following order:
|
||||
|
||||
1. `contents.on('ipc-message')`
|
||||
2. `contents.mainFrame.on(channel)`
|
||||
3. `contents.ipc.on(channel)`
|
||||
4. `ipcMain.on(channel)`
|
||||
|
||||
Handlers registered with `invoke` will be checked in the following order. The
|
||||
first one that is defined will be called, the rest will be ignored.
|
||||
|
||||
1. `contents.mainFrame.handle(channel)`
|
||||
2. `contents.handle(channel)`
|
||||
3. `ipcMain.handle(channel)`
|
||||
|
||||
In most cases, only the main frame of a WebContents can send or receive IPC
|
||||
messages. However, if the `nodeIntegrationInSubFrames` option is enabled, it is
|
||||
possible for child frames to send and receive IPC messages also. The
|
||||
[`WebContents.ipc`](web-contents.md#contentsipc-readonly) interface may be more
|
||||
convenient when `nodeIntegrationInSubFrames` is not enabled.
|
||||
|
||||
#### `frame.url` _Readonly_
|
||||
|
||||
A `string` representing the current URL of the frame.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
startDrag: (fileName) => {
|
||||
|
||||
@@ -22,7 +22,6 @@ In `preload.js` use the [`contextBridge`] to inject a method `window.electron.st
|
||||
|
||||
```js
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
startDrag: (fileName) => {
|
||||
|
||||
@@ -350,7 +350,7 @@ app.whenReady().then(() => {
|
||||
|
||||
## Optional: Debugging from VS Code
|
||||
|
||||
If you want to debug your application using VS Code, you have need attach VS Code to
|
||||
If you want to debug your application using VS Code, you need to attach VS Code to
|
||||
both the main and renderer processes. Here is a sample configuration for you to
|
||||
run. Create a launch.json configuration in a new `.vscode` folder in your project:
|
||||
|
||||
|
||||
@@ -2,7 +2,4 @@ import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
|
||||
|
||||
const ipcMain = new IpcMainImpl();
|
||||
|
||||
// Do not throw exception when channel name is "error".
|
||||
ipcMain.on('error', () => {});
|
||||
|
||||
export default ipcMain;
|
||||
|
||||
@@ -9,12 +9,15 @@ import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
|
||||
import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
|
||||
import { MessagePortMain } from '@electron/internal/browser/message-port-main';
|
||||
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
|
||||
import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
|
||||
|
||||
// session is not used here, the purpose is to make sure session is initialized
|
||||
// before the webContents module.
|
||||
// eslint-disable-next-line
|
||||
session
|
||||
|
||||
const webFrameMainBinding = process._linkedBinding('electron_browser_web_frame_main');
|
||||
|
||||
let nextId = 0;
|
||||
const getNextId = function () {
|
||||
return ++nextId;
|
||||
@@ -556,6 +559,12 @@ WebContents.prototype._init = function () {
|
||||
|
||||
this._windowOpenHandler = null;
|
||||
|
||||
const ipc = new IpcMainImpl();
|
||||
Object.defineProperty(this, 'ipc', {
|
||||
get () { return ipc; },
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
// Dispatch IPC messages to the ipc module.
|
||||
this.on('-ipc-message' as any, function (this: Electron.WebContents, event: Electron.IpcMainEvent, internal: boolean, channel: string, args: any[]) {
|
||||
addSenderFrameToEvent(event);
|
||||
@@ -564,6 +573,9 @@ WebContents.prototype._init = function () {
|
||||
} else {
|
||||
addReplyToEvent(event);
|
||||
this.emit('ipc-message', event, channel, ...args);
|
||||
const maybeWebFrame = webFrameMainBinding.fromIdOrNull(event.processId, event.frameId);
|
||||
maybeWebFrame && maybeWebFrame.ipc.emit(channel, event, ...args);
|
||||
ipc.emit(channel, event, ...args);
|
||||
ipcMain.emit(channel, event, ...args);
|
||||
}
|
||||
});
|
||||
@@ -575,8 +587,10 @@ WebContents.prototype._init = function () {
|
||||
console.error(`Error occurred in handler for '${channel}':`, error);
|
||||
event.sendReply({ error: error.toString() });
|
||||
};
|
||||
const target = internal ? ipcMainInternal : ipcMain;
|
||||
if ((target as any)._invokeHandlers.has(channel)) {
|
||||
const maybeWebFrame = webFrameMainBinding.fromIdOrNull(event.processId, event.frameId);
|
||||
const targets: (ElectronInternal.IpcMainInternal| undefined)[] = internal ? [ipcMainInternal] : [maybeWebFrame && maybeWebFrame.ipc, ipc, ipcMain];
|
||||
const target = targets.find(target => target && (target as any)._invokeHandlers.has(channel));
|
||||
if (target) {
|
||||
(target as any)._invokeHandlers.get(channel)(event, ...args);
|
||||
} else {
|
||||
event._throw(`No handler registered for '${channel}'`);
|
||||
@@ -590,10 +604,13 @@ WebContents.prototype._init = function () {
|
||||
ipcMainInternal.emit(channel, event, ...args);
|
||||
} else {
|
||||
addReplyToEvent(event);
|
||||
if (this.listenerCount('ipc-message-sync') === 0 && ipcMain.listenerCount(channel) === 0) {
|
||||
const maybeWebFrame = webFrameMainBinding.fromIdOrNull(event.processId, event.frameId);
|
||||
if (this.listenerCount('ipc-message-sync') === 0 && ipc.listenerCount(channel) === 0 && ipcMain.listenerCount(channel) === 0 && (!maybeWebFrame || maybeWebFrame.ipc.listenerCount(channel) === 0)) {
|
||||
console.warn(`WebContents #${this.id} called ipcRenderer.sendSync() with '${channel}' channel without listeners.`);
|
||||
}
|
||||
this.emit('ipc-message-sync', event, channel, ...args);
|
||||
maybeWebFrame && maybeWebFrame.ipc.emit(channel, event, ...args);
|
||||
ipc.emit(channel, event, ...args);
|
||||
ipcMain.emit(channel, event, ...args);
|
||||
}
|
||||
});
|
||||
@@ -601,6 +618,9 @@ WebContents.prototype._init = function () {
|
||||
this.on('-ipc-ports' as any, function (event: Electron.IpcMainEvent, internal: boolean, channel: string, message: any, ports: any[]) {
|
||||
addSenderFrameToEvent(event);
|
||||
event.ports = ports.map(p => new MessagePortMain(p));
|
||||
const maybeWebFrame = webFrameMainBinding.fromIdOrNull(event.processId, event.frameId);
|
||||
maybeWebFrame && maybeWebFrame.ipc.emit(channel, event, message);
|
||||
ipc.emit(channel, event, message);
|
||||
ipcMain.emit(channel, event, message);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { MessagePortMain } from '@electron/internal/browser/message-port-main';
|
||||
import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
|
||||
|
||||
const { WebFrameMain, fromId } = process._linkedBinding('electron_browser_web_frame_main');
|
||||
|
||||
Object.defineProperty(WebFrameMain.prototype, 'ipc', {
|
||||
get () {
|
||||
const ipc = new IpcMainImpl();
|
||||
Object.defineProperty(this, 'ipc', { value: ipc });
|
||||
return ipc;
|
||||
}
|
||||
});
|
||||
|
||||
WebFrameMain.prototype.send = function (channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument');
|
||||
|
||||
@@ -4,6 +4,13 @@ import { IpcMainInvokeEvent } from 'electron/main';
|
||||
export class IpcMainImpl extends EventEmitter {
|
||||
private _invokeHandlers: Map<string, (e: IpcMainInvokeEvent, ...args: any[]) => void> = new Map();
|
||||
|
||||
constructor () {
|
||||
super();
|
||||
|
||||
// Do not throw exception when channel name is "error".
|
||||
this.on('error', () => {});
|
||||
}
|
||||
|
||||
handle: Electron.IpcMain['handle'] = (method, fn) => {
|
||||
if (this._invokeHandlers.has(method)) {
|
||||
throw new Error(`Attempted to register a second handler for '${method}'`);
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
|
||||
|
||||
export const ipcMainInternal = new IpcMainImpl() as ElectronInternal.IpcMainInternal;
|
||||
|
||||
// Do not throw exception when channel name is "error".
|
||||
ipcMainInternal.on('error', () => {});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"version": "21.0.0-nightly.20220803",
|
||||
"version": "21.0.0-alpha.5",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -115,3 +115,5 @@ short-circuit_permissions_checks_in_mediastreamdevicescontroller.patch
|
||||
chore_add_electron_deps_to_gitignores.patch
|
||||
chore_allow_chromium_to_handle_synthetic_mouse_events_for_touch.patch
|
||||
add_maximized_parameter_to_linuxui_getwindowframeprovider.patch
|
||||
add_electron_deps_to_license_credits_file.patch
|
||||
feat_add_set_can_resize_mutator.patch
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Charles Kerr <charles@charleskerr.com>
|
||||
Date: Tue, 9 Aug 2022 12:35:36 -0500
|
||||
Subject: add electron deps to license credits file
|
||||
|
||||
Ensure that licenses for the dependencies introduced by Electron
|
||||
are included in `LICENSES.chromium.html`
|
||||
|
||||
diff --git a/tools/licenses.py b/tools/licenses.py
|
||||
index a58dbf44370baabbfa2986c734c96a210cc16f1d..1d6934460f788ab76275710e727fb062f5c92b5b 100755
|
||||
--- a/tools/licenses.py
|
||||
+++ b/tools/licenses.py
|
||||
@@ -347,6 +347,32 @@ SPECIAL_CASES = {
|
||||
"License File":
|
||||
"/third_party/swiftshader/third_party/SPIRV-Headers/LICENSE",
|
||||
},
|
||||
+
|
||||
+ os.path.join('third_party', 'electron_node'): {
|
||||
+ "Name": "Node.js",
|
||||
+ "URL": "https://github.com/nodejs/node",
|
||||
+ "License": "MIT",
|
||||
+ "License File": "/third_party/electron_node/LICENSE",
|
||||
+ },
|
||||
+ os.path.join('third_party', 'squirrel.mac'): {
|
||||
+ "Name": "Squirrel",
|
||||
+ "URL": "https://github.com/Squirrel/Squirrel.Mac",
|
||||
+ "License": "MIT",
|
||||
+ "License File": "/third_party/squirrel.mac/LICENSE",
|
||||
+ },
|
||||
+ os.path.join('third_party', 'squirrel.mac', 'vendor', 'mantle'): {
|
||||
+ "Name": "Mantle",
|
||||
+ "URL": "https://github.com/Mantle/Mantle",
|
||||
+ "License": "MIT",
|
||||
+ "License File": "/third_party/squirrel.mac/vendor/mantle/LICENSE.md",
|
||||
+ },
|
||||
+ os.path.join('third_party', 'squirrel.mac', 'vendor', 'ReactiveObjC'): {
|
||||
+ "Name": "ReactiveObjC",
|
||||
+ "URL": "https://github.com/ReactiveCocoa/ReactiveObjC",
|
||||
+ "License": "MIT",
|
||||
+ "License File":
|
||||
+ "/third_party/squirrel.mac/vendor/ReactiveObjC/LICENSE.md",
|
||||
+ },
|
||||
}
|
||||
|
||||
# Special value for 'License File' field used to indicate that the license file
|
||||
27
patches/chromium/feat_add_set_can_resize_mutator.patch
Normal file
27
patches/chromium/feat_add_set_can_resize_mutator.patch
Normal file
@@ -0,0 +1,27 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com>
|
||||
Date: Tue, 2 Aug 2022 09:30:36 -0700
|
||||
Subject: feat: Add set_can_resize mutator
|
||||
|
||||
Adds a set_can_resize mutator to WidgetDelegate that
|
||||
doesn't emit the OnSizeConstraintsChanged event.
|
||||
This way, we can call set_can_resize from Electron before
|
||||
the widget is initialized to set the value earlier,
|
||||
and in turn, avoid showing a frame at startup
|
||||
for frameless applications.
|
||||
|
||||
diff --git a/ui/views/widget/widget_delegate.h b/ui/views/widget/widget_delegate.h
|
||||
index 431d19f2543a9011de76b941982603ff98afa041..32e07e0c9686e6942a40c4d4775a03068cfd33b5 100644
|
||||
--- a/ui/views/widget/widget_delegate.h
|
||||
+++ b/ui/views/widget/widget_delegate.h
|
||||
@@ -323,6 +323,10 @@ class VIEWS_EXPORT WidgetDelegate {
|
||||
// be cycled through with keyboard focus.
|
||||
virtual void GetAccessiblePanes(std::vector<View*>* panes) {}
|
||||
|
||||
+ // A setter for the can_resize parameter that doesn't
|
||||
+ // emit any events.
|
||||
+ void set_can_resize(bool can_resize) { params_.can_resize = can_resize; }
|
||||
+
|
||||
// Setters for data parameters of the WidgetDelegate. If you use these
|
||||
// setters, there is no need to override the corresponding virtual getters.
|
||||
void SetAccessibleRole(ax::mojom::Role role);
|
||||
@@ -1151,7 +1151,9 @@ bool App::Relaunch(gin::Arguments* js_args) {
|
||||
|
||||
gin_helper::Dictionary options;
|
||||
if (js_args->GetNext(&options)) {
|
||||
if (options.Get("execPath", &exec_path) || options.Get("args", &args))
|
||||
bool has_exec_path = options.Get("execPath", &exec_path);
|
||||
bool has_args = options.Get("args", &args);
|
||||
if (has_exec_path || has_args)
|
||||
override_argv = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -362,6 +362,18 @@ gin::Handle<WebFrameMain> WebFrameMain::From(v8::Isolate* isolate,
|
||||
return handle;
|
||||
}
|
||||
|
||||
// static
|
||||
gin::Handle<WebFrameMain> WebFrameMain::FromOrNull(
|
||||
v8::Isolate* isolate,
|
||||
content::RenderFrameHost* rfh) {
|
||||
if (rfh == nullptr)
|
||||
return gin::Handle<WebFrameMain>();
|
||||
auto* web_frame = FromRenderFrameHost(rfh);
|
||||
if (web_frame)
|
||||
return gin::CreateHandle(isolate, web_frame);
|
||||
return gin::Handle<WebFrameMain>();
|
||||
}
|
||||
|
||||
// static
|
||||
v8::Local<v8::ObjectTemplate> WebFrameMain::FillObjectTemplate(
|
||||
v8::Isolate* isolate,
|
||||
@@ -409,6 +421,20 @@ v8::Local<v8::Value> FromID(gin_helper::ErrorThrower thrower,
|
||||
return WebFrameMain::From(thrower.isolate(), rfh).ToV8();
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> FromIDOrNull(gin_helper::ErrorThrower thrower,
|
||||
int render_process_id,
|
||||
int render_frame_id) {
|
||||
if (!electron::Browser::Get()->is_ready()) {
|
||||
thrower.ThrowError("WebFrameMain is available only after app ready");
|
||||
return v8::Null(thrower.isolate());
|
||||
}
|
||||
|
||||
auto* rfh =
|
||||
content::RenderFrameHost::FromID(render_process_id, render_frame_id);
|
||||
|
||||
return WebFrameMain::FromOrNull(thrower.isolate(), rfh).ToV8();
|
||||
}
|
||||
|
||||
void Initialize(v8::Local<v8::Object> exports,
|
||||
v8::Local<v8::Value> unused,
|
||||
v8::Local<v8::Context> context,
|
||||
@@ -417,6 +443,7 @@ void Initialize(v8::Local<v8::Object> exports,
|
||||
gin_helper::Dictionary dict(isolate, exports);
|
||||
dict.Set("WebFrameMain", WebFrameMain::GetConstructor(context));
|
||||
dict.SetMethod("fromId", &FromID);
|
||||
dict.SetMethod("fromIdOrNull", &FromIDOrNull);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -44,6 +44,9 @@ class WebFrameMain : public gin::Wrappable<WebFrameMain>,
|
||||
static gin::Handle<WebFrameMain> From(
|
||||
v8::Isolate* isolate,
|
||||
content::RenderFrameHost* render_frame_host);
|
||||
static gin::Handle<WebFrameMain> FromOrNull(
|
||||
v8::Isolate* isolate,
|
||||
content::RenderFrameHost* render_frame_host);
|
||||
static WebFrameMain* FromFrameTreeNodeId(int frame_tree_node_id);
|
||||
static WebFrameMain* FromRenderFrameHost(
|
||||
content::RenderFrameHost* render_frame_host);
|
||||
|
||||
@@ -321,43 +321,6 @@ Browser::LoginItemSettings Browser::GetLoginItemSettings(
|
||||
return settings;
|
||||
}
|
||||
|
||||
// Some logic here copied from GetLoginItemForApp in base/mac/mac_util.mm
|
||||
void RemoveFromLoginItems() {
|
||||
#pragma clang diagnostic push // https://crbug.com/1154377
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
base::ScopedCFTypeRef<LSSharedFileListRef> login_items(
|
||||
LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL));
|
||||
if (!login_items.get()) {
|
||||
LOG(ERROR) << "Couldn't get a Login Items list.";
|
||||
return;
|
||||
}
|
||||
|
||||
base::scoped_nsobject<NSArray> login_items_array(
|
||||
base::mac::CFToNSCast(LSSharedFileListCopySnapshot(login_items, NULL)));
|
||||
NSURL* url = [NSURL fileURLWithPath:[base::mac::MainBundle() bundlePath]];
|
||||
for (id login_item in login_items_array.get()) {
|
||||
LSSharedFileListItemRef item =
|
||||
reinterpret_cast<LSSharedFileListItemRef>(login_item);
|
||||
|
||||
// kLSSharedFileListDoNotMountVolumes is used so that we don't trigger
|
||||
// mounting when it's not expected by a user. Just listing the login
|
||||
// items should not cause any side-effects.
|
||||
base::ScopedCFTypeRef<CFErrorRef> error;
|
||||
base::ScopedCFTypeRef<CFURLRef> item_url_ref(
|
||||
LSSharedFileListItemCopyResolvedURL(
|
||||
item, kLSSharedFileListDoNotMountVolumes, error.InitializeInto()));
|
||||
|
||||
if (!error && item_url_ref) {
|
||||
base::ScopedCFTypeRef<CFURLRef> item_url(item_url_ref);
|
||||
if (CFEqual(item_url, url)) {
|
||||
LSSharedFileListItemRemove(login_items, item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
void Browser::SetLoginItemSettings(LoginItemSettings settings) {
|
||||
#if defined(MAS_BUILD)
|
||||
if (!platform_util::SetLoginItemEnabled(settings.open_at_login)) {
|
||||
@@ -367,7 +330,7 @@ void Browser::SetLoginItemSettings(LoginItemSettings settings) {
|
||||
if (settings.open_at_login) {
|
||||
base::mac::AddToLoginItems(settings.open_as_hidden);
|
||||
} else {
|
||||
RemoveFromLoginItems();
|
||||
base::mac::RemoveFromLoginItems();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1487,9 +1487,6 @@ bool ElectronBrowserClient::WillCreateURLLoaderFactory(
|
||||
std::move(proxied_receiver), std::move(target_factory_remote),
|
||||
std::move(header_client_receiver), type);
|
||||
|
||||
if (bypass_redirect_checks)
|
||||
*bypass_redirect_checks = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -281,7 +281,24 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
|
||||
new ElectronDesktopWindowTreeHostLinux(this, native_widget);
|
||||
#endif
|
||||
|
||||
// Ref https://github.com/electron/electron/issues/30760
|
||||
// Set the can_resize param before initializing the widget.
|
||||
// When resizable_ is true, this causes the WS_THICKFRAME style
|
||||
// to be passed into CreateWindowEx and SetWindowLong calls in
|
||||
// WindowImpl::Init and HwndMessageHandler::SizeConstraintsChanged
|
||||
// respectively. As a result, the Windows 7 frame doesn't show,
|
||||
// but it isn't clear why this is the case.
|
||||
// When resizable_ is false, WS_THICKFRAME is not passed into the
|
||||
// SetWindowLong call, so the Windows 7 frame still shows.
|
||||
// One workaround would be to call set_can_resize(true) here,
|
||||
// and then move the SetCanResize(resizable_) call after the
|
||||
// SetWindowLong call around line 365, but that's a much larger change.
|
||||
set_can_resize(true);
|
||||
widget()->Init(std::move(params));
|
||||
|
||||
// When the workaround above is not needed anymore, only this
|
||||
// call should be necessary.
|
||||
// With the workaround in place, this call doesn't do anything.
|
||||
SetCanResize(resizable_);
|
||||
|
||||
bool fullscreen = false;
|
||||
|
||||
@@ -50,8 +50,8 @@ END
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 21,0,0,20220803
|
||||
PRODUCTVERSION 21,0,0,20220803
|
||||
FILEVERSION 21,0,0,5
|
||||
PRODUCTVERSION 21,0,0,5
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
||||
@@ -56,10 +56,7 @@ bool ElectronSerialDelegate::HasPortPermission(
|
||||
content::RenderFrameHost* frame,
|
||||
const device::mojom::SerialPortInfo& port) {
|
||||
auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
|
||||
auto* browser_context = web_contents->GetBrowserContext();
|
||||
auto* chooser_context =
|
||||
SerialChooserContextFactory::GetForBrowserContext(browser_context);
|
||||
return chooser_context->HasPortPermission(
|
||||
return GetChooserContext(frame)->HasPortPermission(
|
||||
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin(), port,
|
||||
frame);
|
||||
}
|
||||
@@ -91,8 +88,7 @@ void ElectronSerialDelegate::RevokePortPermissionWebInitiated(
|
||||
const device::mojom::SerialPortInfo* ElectronSerialDelegate::GetPortInfo(
|
||||
content::RenderFrameHost* frame,
|
||||
const base::UnguessableToken& token) {
|
||||
// TODO(nornagon/jkleinsc): pass this on to the chooser context
|
||||
return nullptr;
|
||||
return GetChooserContext(frame)->GetPortInfo(token);
|
||||
}
|
||||
|
||||
SerialChooserController* ElectronSerialDelegate::ControllerForFrame(
|
||||
|
||||
@@ -103,6 +103,8 @@ void SerialChooserContext::GrantPortPermission(
|
||||
const url::Origin& origin,
|
||||
const device::mojom::SerialPortInfo& port,
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
port_info_.insert({port.token, port.Clone()});
|
||||
|
||||
auto* permission_manager = static_cast<ElectronPermissionManager*>(
|
||||
browser_context_->GetPermissionControllerDelegate());
|
||||
return permission_manager->GrantDevicePermission(
|
||||
@@ -190,6 +192,9 @@ base::WeakPtr<SerialChooserContext> SerialChooserContext::AsWeakPtr() {
|
||||
}
|
||||
|
||||
void SerialChooserContext::OnPortAdded(device::mojom::SerialPortInfoPtr port) {
|
||||
if (!base::Contains(port_info_, port->token))
|
||||
port_info_.insert({port->token, port->Clone()});
|
||||
|
||||
for (auto& observer : port_observer_list_)
|
||||
observer.OnPortAdded(*port);
|
||||
}
|
||||
@@ -198,6 +203,8 @@ void SerialChooserContext::OnPortRemoved(
|
||||
device::mojom::SerialPortInfoPtr port) {
|
||||
for (auto& observer : port_observer_list_)
|
||||
observer.OnPortRemoved(*port);
|
||||
|
||||
port_info_.erase(port->token);
|
||||
}
|
||||
|
||||
void SerialChooserContext::EnsurePortManagerConnection() {
|
||||
@@ -218,6 +225,15 @@ void SerialChooserContext::SetUpPortManagerConnection(
|
||||
base::Unretained(this)));
|
||||
|
||||
port_manager_->SetClient(client_receiver_.BindNewPipeAndPassRemote());
|
||||
port_manager_->GetDevices(base::BindOnce(&SerialChooserContext::OnGetDevices,
|
||||
weak_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
void SerialChooserContext::OnGetDevices(
|
||||
std::vector<device::mojom::SerialPortInfoPtr> ports) {
|
||||
for (auto& port : ports)
|
||||
port_info_.insert({port->token, std::move(port)});
|
||||
is_initialized_ = true;
|
||||
}
|
||||
|
||||
void SerialChooserContext::OnPortManagerConnectionError() {
|
||||
|
||||
@@ -97,6 +97,7 @@ class SerialChooserContext : public KeyedService,
|
||||
void EnsurePortManagerConnection();
|
||||
void SetUpPortManagerConnection(
|
||||
mojo::PendingRemote<device::mojom::SerialPortManager> manager);
|
||||
void OnGetDevices(std::vector<device::mojom::SerialPortInfoPtr> ports);
|
||||
void OnPortManagerConnectionError();
|
||||
void RevokeObjectPermissionInternal(const url::Origin& origin,
|
||||
const base::Value& object,
|
||||
|
||||
@@ -136,6 +136,7 @@ void stop_and_close_uv_loop(uv_loop_t* loop) {
|
||||
break;
|
||||
|
||||
DCHECK_EQ(0, uv_loop_alive(loop));
|
||||
uv_loop_close(loop);
|
||||
}
|
||||
|
||||
bool g_is_initialized = false;
|
||||
|
||||
@@ -370,9 +370,11 @@ describe('app module', () => {
|
||||
server!.once('error', error => done(error));
|
||||
server!.on('connection', client => {
|
||||
client.once('data', data => {
|
||||
if (String(data) === 'false' && state === 'none') {
|
||||
if (String(data) === '--first' && state === 'none') {
|
||||
state = 'first-launch';
|
||||
} else if (String(data) === 'true' && state === 'first-launch') {
|
||||
} else if (String(data) === '--second' && state === 'first-launch') {
|
||||
state = 'second-launch';
|
||||
} else if (String(data) === '--third' && state === 'second-launch') {
|
||||
done();
|
||||
} else {
|
||||
done(`Unexpected state: "${state}", data: "${data}"`);
|
||||
@@ -381,7 +383,7 @@ describe('app module', () => {
|
||||
});
|
||||
|
||||
const appPath = path.join(fixturesPath, 'api', 'relaunch');
|
||||
const child = cp.spawn(process.execPath, [appPath]);
|
||||
const child = cp.spawn(process.execPath, [appPath, '--first']);
|
||||
child.stdout.on('data', (c) => console.log(c.toString()));
|
||||
child.stderr.on('data', (c) => console.log(c.toString()));
|
||||
child.on('exit', (code, signal) => {
|
||||
|
||||
@@ -5255,7 +5255,8 @@ describe('BrowserWindow module', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('contextIsolation option with and without sandbox option', () => {
|
||||
// TODO (jkleinsc) renable these tests on mas arm64
|
||||
ifdescribe(!process.mas || process.arch !== 'arm64')('contextIsolation option with and without sandbox option', () => {
|
||||
const expectedContextData = {
|
||||
preloadContext: {
|
||||
preloadProperty: 'number',
|
||||
|
||||
@@ -3,8 +3,13 @@ import { expect } from 'chai';
|
||||
import { BrowserWindow, ipcMain, IpcMainInvokeEvent, MessageChannelMain, WebContents } from 'electron/main';
|
||||
import { closeAllWindows } from './window-helpers';
|
||||
import { emittedOnce } from './events-helpers';
|
||||
import { defer } from './spec-helpers';
|
||||
import * as path from 'path';
|
||||
import * as http from 'http';
|
||||
import { AddressInfo } from 'net';
|
||||
|
||||
const v8Util = process._linkedBinding('electron_common_v8_util');
|
||||
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
||||
|
||||
describe('ipc module', () => {
|
||||
describe('invoke', () => {
|
||||
@@ -90,7 +95,7 @@ describe('ipc module', () => {
|
||||
});
|
||||
|
||||
it('throws an error when invoking a handler that was removed', async () => {
|
||||
ipcMain.handle('test', () => {});
|
||||
ipcMain.handle('test', () => { });
|
||||
ipcMain.removeHandler('test');
|
||||
const done = new Promise<void>(resolve => ipcMain.once('result', (e, arg) => {
|
||||
expect(arg.error).to.match(/No handler registered/);
|
||||
@@ -101,9 +106,9 @@ describe('ipc module', () => {
|
||||
});
|
||||
|
||||
it('forbids multiple handlers', async () => {
|
||||
ipcMain.handle('test', () => {});
|
||||
ipcMain.handle('test', () => { });
|
||||
try {
|
||||
expect(() => { ipcMain.handle('test', () => {}); }).to.throw(/second handler/);
|
||||
expect(() => { ipcMain.handle('test', () => { }); }).to.throw(/second handler/);
|
||||
} finally {
|
||||
ipcMain.removeHandler('test');
|
||||
}
|
||||
@@ -563,4 +568,195 @@ describe('ipc module', () => {
|
||||
generateTests('WebContents.postMessage', contents => contents.postMessage.bind(contents));
|
||||
generateTests('WebFrameMain.postMessage', contents => contents.mainFrame.postMessage.bind(contents.mainFrame));
|
||||
});
|
||||
|
||||
describe('WebContents.ipc', () => {
|
||||
afterEach(closeAllWindows);
|
||||
|
||||
it('receives ipc messages sent from the WebContents', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.send(\'test\', 42)');
|
||||
const [, num] = await emittedOnce(w.webContents.ipc, 'test');
|
||||
expect(num).to.equal(42);
|
||||
});
|
||||
|
||||
it('receives sync-ipc messages sent from the WebContents', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
w.webContents.ipc.on('test', (event, arg) => {
|
||||
event.returnValue = arg * 2;
|
||||
});
|
||||
const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.sendSync(\'test\', 42)');
|
||||
expect(result).to.equal(42 * 2);
|
||||
});
|
||||
|
||||
it('receives postMessage messages sent from the WebContents, w/ MessagePorts', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.postMessage(\'test\', null, [(new MessageChannel).port1])');
|
||||
const [event] = await emittedOnce(w.webContents.ipc, 'test');
|
||||
expect(event.ports.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('handles invoke messages sent from the WebContents', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
w.webContents.ipc.handle('test', (_event, arg) => arg * 2);
|
||||
const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.invoke(\'test\', 42)');
|
||||
expect(result).to.equal(42 * 2);
|
||||
});
|
||||
|
||||
it('cascades to ipcMain', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
let gotFromIpcMain = false;
|
||||
const ipcMainReceived = new Promise<void>(resolve => ipcMain.on('test', () => { gotFromIpcMain = true; resolve(); }));
|
||||
const ipcReceived = new Promise<boolean>(resolve => w.webContents.ipc.on('test', () => { resolve(gotFromIpcMain); }));
|
||||
defer(() => ipcMain.removeAllListeners('test'));
|
||||
w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.send(\'test\', 42)');
|
||||
|
||||
// assert that they are delivered in the correct order
|
||||
expect(await ipcReceived).to.be.false();
|
||||
await ipcMainReceived;
|
||||
});
|
||||
|
||||
it('overrides ipcMain handlers', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
w.webContents.ipc.handle('test', (_event, arg) => arg * 2);
|
||||
ipcMain.handle('test', () => { throw new Error('should not be called'); });
|
||||
defer(() => ipcMain.removeHandler('test'));
|
||||
const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.invoke(\'test\', 42)');
|
||||
expect(result).to.equal(42 * 2);
|
||||
});
|
||||
|
||||
it('falls back to ipcMain handlers', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
ipcMain.handle('test', (_event, arg) => { return arg * 2; });
|
||||
defer(() => ipcMain.removeHandler('test'));
|
||||
const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.invoke(\'test\', 42)');
|
||||
expect(result).to.equal(42 * 2);
|
||||
});
|
||||
|
||||
it('receives ipcs from child frames', async () => {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('content-type', 'text/html');
|
||||
res.end('');
|
||||
});
|
||||
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
|
||||
const port = (server.address() as AddressInfo).port;
|
||||
defer(() => {
|
||||
server.close();
|
||||
});
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegrationInSubFrames: true, preload: path.resolve(fixturesPath, 'preload-expose-ipc.js') } });
|
||||
// Preloads don't run in about:blank windows, and file:// urls can't be loaded in iframes, so use a blank http page.
|
||||
await w.loadURL(`data:text/html,<iframe src="http://localhost:${port}"></iframe>`);
|
||||
w.webContents.mainFrame.frames[0].executeJavaScript('ipc.send(\'test\', 42)');
|
||||
const [, arg] = await emittedOnce(w.webContents.ipc, 'test');
|
||||
expect(arg).to.equal(42);
|
||||
});
|
||||
});
|
||||
|
||||
describe('WebFrameMain.ipc', () => {
|
||||
afterEach(closeAllWindows);
|
||||
it('responds to ipc messages in the main frame', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.send(\'test\', 42)');
|
||||
const [, arg] = await emittedOnce(w.webContents.mainFrame.ipc, 'test');
|
||||
expect(arg).to.equal(42);
|
||||
});
|
||||
|
||||
it('responds to sync ipc messages in the main frame', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
w.webContents.mainFrame.ipc.on('test', (event, arg) => {
|
||||
event.returnValue = arg * 2;
|
||||
});
|
||||
const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.sendSync(\'test\', 42)');
|
||||
expect(result).to.equal(42 * 2);
|
||||
});
|
||||
|
||||
it('receives postMessage messages sent from the WebContents, w/ MessagePorts', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.postMessage(\'test\', null, [(new MessageChannel).port1])');
|
||||
const [event] = await emittedOnce(w.webContents.mainFrame.ipc, 'test');
|
||||
expect(event.ports.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('handles invoke messages sent from the WebContents', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
w.webContents.mainFrame.ipc.handle('test', (_event, arg) => arg * 2);
|
||||
const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.invoke(\'test\', 42)');
|
||||
expect(result).to.equal(42 * 2);
|
||||
});
|
||||
|
||||
it('cascades to WebContents and ipcMain', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
let gotFromIpcMain = false;
|
||||
let gotFromWebContents = false;
|
||||
const ipcMainReceived = new Promise<void>(resolve => ipcMain.on('test', () => { gotFromIpcMain = true; resolve(); }));
|
||||
const ipcWebContentsReceived = new Promise<boolean>(resolve => w.webContents.ipc.on('test', () => { gotFromWebContents = true; resolve(gotFromIpcMain); }));
|
||||
const ipcReceived = new Promise<boolean>(resolve => w.webContents.mainFrame.ipc.on('test', () => { resolve(gotFromWebContents); }));
|
||||
defer(() => ipcMain.removeAllListeners('test'));
|
||||
w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.send(\'test\', 42)');
|
||||
|
||||
// assert that they are delivered in the correct order
|
||||
expect(await ipcReceived).to.be.false();
|
||||
expect(await ipcWebContentsReceived).to.be.false();
|
||||
await ipcMainReceived;
|
||||
});
|
||||
|
||||
it('overrides ipcMain handlers', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
w.webContents.mainFrame.ipc.handle('test', (_event, arg) => arg * 2);
|
||||
ipcMain.handle('test', () => { throw new Error('should not be called'); });
|
||||
defer(() => ipcMain.removeHandler('test'));
|
||||
const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.invoke(\'test\', 42)');
|
||||
expect(result).to.equal(42 * 2);
|
||||
});
|
||||
|
||||
it('overrides WebContents handlers', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
w.webContents.ipc.handle('test', () => { throw new Error('should not be called'); });
|
||||
w.webContents.mainFrame.ipc.handle('test', (_event, arg) => arg * 2);
|
||||
ipcMain.handle('test', () => { throw new Error('should not be called'); });
|
||||
defer(() => ipcMain.removeHandler('test'));
|
||||
const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.invoke(\'test\', 42)');
|
||||
expect(result).to.equal(42 * 2);
|
||||
});
|
||||
|
||||
it('falls back to WebContents handlers', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
|
||||
w.loadURL('about:blank');
|
||||
w.webContents.ipc.handle('test', (_event, arg) => { return arg * 2; });
|
||||
const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.invoke(\'test\', 42)');
|
||||
expect(result).to.equal(42 * 2);
|
||||
});
|
||||
|
||||
it('receives ipcs from child frames', async () => {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('content-type', 'text/html');
|
||||
res.end('');
|
||||
});
|
||||
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
|
||||
const port = (server.address() as AddressInfo).port;
|
||||
defer(() => {
|
||||
server.close();
|
||||
});
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegrationInSubFrames: true, preload: path.resolve(fixturesPath, 'preload-expose-ipc.js') } });
|
||||
// Preloads don't run in about:blank windows, and file:// urls can't be loaded in iframes, so use a blank http page.
|
||||
await w.loadURL(`data:text/html,<iframe src="http://localhost:${port}"></iframe>`);
|
||||
w.webContents.mainFrame.frames[0].executeJavaScript('ipc.send(\'test\', 42)');
|
||||
w.webContents.mainFrame.ipc.on('test', () => { throw new Error('should not be called'); });
|
||||
const [, arg] = await emittedOnce(w.webContents.mainFrame.frames[0].ipc, 'test');
|
||||
expect(arg).to.equal(42);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
14
spec-main/fixtures/preload-expose-ipc.js
Normal file
14
spec-main/fixtures/preload-expose-ipc.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
|
||||
// NOTE: Never do this in an actual app! Very insecure!
|
||||
contextBridge.exposeInMainWorld('ipc', {
|
||||
send (...args) {
|
||||
return ipcRenderer.send(...args);
|
||||
},
|
||||
sendSync (...args) {
|
||||
return ipcRenderer.sendSync(...args);
|
||||
},
|
||||
invoke (...args) {
|
||||
return ipcRenderer.invoke(...args);
|
||||
}
|
||||
});
|
||||
10
spec/fixtures/api/relaunch/main.js
vendored
10
spec/fixtures/api/relaunch/main.js
vendored
@@ -11,11 +11,15 @@ app.whenReady().then(() => {
|
||||
const lastArg = process.argv[process.argv.length - 1];
|
||||
const client = net.connect(socketPath);
|
||||
client.once('connect', () => {
|
||||
client.end(String(lastArg === '--second'));
|
||||
client.end(lastArg);
|
||||
});
|
||||
client.once('end', () => {
|
||||
if (lastArg !== '--second') {
|
||||
app.relaunch({ args: process.argv.slice(1).concat('--second') });
|
||||
if (lastArg === '--first') {
|
||||
// Once without execPath specified
|
||||
app.relaunch({ args: process.argv.slice(1, -1).concat('--second') });
|
||||
} else if (lastArg === '--second') {
|
||||
// And once with execPath specified
|
||||
app.relaunch({ execPath: process.argv[0], args: process.argv.slice(1, -1).concat('--third') });
|
||||
}
|
||||
app.exit(0);
|
||||
});
|
||||
|
||||
1
typings/internal-ambient.d.ts
vendored
1
typings/internal-ambient.d.ts
vendored
@@ -237,6 +237,7 @@ declare namespace NodeJS {
|
||||
_linkedBinding(name: 'electron_browser_web_frame_main'): {
|
||||
WebFrameMain: typeof Electron.WebFrameMain;
|
||||
fromId(processId: number, routingId: number): Electron.WebFrameMain;
|
||||
fromIdOrNull(processId: number, routingId: number): Electron.WebFrameMain;
|
||||
}
|
||||
_linkedBinding(name: 'electron_renderer_crash_reporter'): Electron.CrashReporter;
|
||||
_linkedBinding(name: 'electron_renderer_ipc'): { ipc: IpcRendererBinding };
|
||||
|
||||
Reference in New Issue
Block a user