Compare commits

...

19 Commits

Author SHA1 Message Date
Sudowoodo Release Bot
76e8536ca8 Bump v21.0.0-alpha.5 2022-08-22 16:25:16 -07:00
trop[bot]
b075434447 fix: IPC emit order in -ipc-ports handler (#35364)
Co-authored-by: Milan Burda <miburda@microsoft.com>
2022-08-18 18:00:45 -07:00
Sudowoodo Release Bot
9673d29ebe Bump v21.0.0-alpha.4 2022-08-18 12:05:12 -07:00
trop[bot]
5e6d152231 fix: don't bypass redirect checks (#35366)
Co-authored-by: Jeremy Rose <japthorp@slack-corp.com>
2022-08-18 10:09:18 -07:00
trop[bot]
b31a4b4394 fix: Frameless window shows frame while opening (#35353)
* fix: Frameless window shows frame while opening

* Clarify comments

* Inline setter

* Edit comment

Co-authored-by: Raymond Zhao <7199958+rzhao271@users.noreply.github.com>
2022-08-17 15:20:20 -07:00
trop[bot]
28ca18a912 feat: add WebContents.ipc (#35231)
feat: add WebContents.ipc (#34959)

Co-authored-by: Jeremy Rose <jeremya@chromium.org>
2022-08-17 09:50:15 -07:00
trop[bot]
8caea9006a fix: add uv_loop_close when object release to fix crash (#35336)
Co-authored-by: yangzuohui <yangzuohui@bytedance.com>
2022-08-16 17:03:05 +09:00
trop[bot]
855536bcb9 refactor: simplify Browser::SetLoginItemSettings (#35329)
refactor: simplify Browser::SetLoginItemSettings

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2022-08-15 16:43:48 -04:00
trop[bot]
1978a0b5fe fix: serialPort.open() failing (#35339)
fix: serialPort.open() failing

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2022-08-15 13:53:00 -04:00
Sudowoodo Release Bot
fe70152b43 Bump v21.0.0-alpha.3 2022-08-15 08:42:41 -07:00
trop[bot]
9b38a25206 docs: add Electron deps to license credits file (#35332)
* docs: add Electron deps to license credits file

* fixup! docs: add Electron deps to license credits file

remove nan; it is dev-only

Co-authored-by: Charles Kerr <charles@charleskerr.com>
2022-08-15 10:42:39 -04:00
trop[bot]
a66d3bf206 docs: removes unused import in preload script (#35334)
* Remove unused import of path

This import gives out the error in the preload script:

Error: module not found: path
    at preloadRequire

* Remove import path as it is not used in the script

note: Removes import path as it is not used in the script

Co-authored-by: Rhitik Bhatt <bhattrhitik95@gmail.com>
2022-08-15 17:06:50 +09:00
Sudowoodo Release Bot
752b10f16f Bump v21.0.0-alpha.2 2022-08-11 09:37:32 -07:00
trop[bot]
5803a67963 docs: fix getStoragePath return type (#35296)
Fixes #35255

Co-authored-by: Samuel Attard <sam@electronjs.org>
2022-08-10 22:42:17 +02:00
trop[bot]
40058af821 fix(docs): fix a typo in section on debugging with VSCode (#35285)
fix(docs): fix a typo

Co-authored-by: Trang Le <trang.thule@zoho.com>
2022-08-09 15:30:29 -07:00
trop[bot]
64b40ea69c docs: update tray docs with info for mac menubar icons (#35221)
Co-authored-by: Brad Carter <16466430+carterbs@users.noreply.github.com>
2022-08-09 09:01:10 +09:00
trop[bot]
7c6e16975f fix: app.relaunch loses args when execPath specified (#35254)
fix: app.relaunch loses args when execPath specified (#35108)

Co-authored-by: Aaron Meriwether <me@ameriwether.com>
2022-08-09 08:54:58 +09:00
trop[bot]
c9e7f33f7e test: temporarily disable tests on mas arm64 that are causing a crash (#35247)
* test: temporarily disable test on mas arm64 that is causing crash

* disable the right test

* chore: speculative fix for CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER crash

* enable all the tests

* Revert "chore: speculative fix for CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER crash"

This reverts commit b7c8ef364c.

* test: disable tests that crash on mas arm64

Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2022-08-08 10:02:58 -04:00
Sudowoodo Release Bot
98aa0ef090 Bump v21.0.0-alpha.1 2022-08-03 07:37:56 -07:00
34 changed files with 495 additions and 83 deletions

View File

@@ -1 +1 @@
21.0.0-nightly.20220803
21.0.0-alpha.5

View File

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

View File

@@ -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])`

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
const { contextBridge, ipcRenderer } = require('electron')
const path = require('path')
contextBridge.exposeInMainWorld('electron', {
startDrag: (fileName) => {

View File

@@ -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) => {

View File

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

View File

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

View File

@@ -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);
});

View File

@@ -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');

View File

@@ -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}'`);

View File

@@ -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', () => {});

View File

@@ -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": {

View File

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

View File

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

View 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);

View File

@@ -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;
}

View File

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

View File

@@ -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);

View File

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

View File

@@ -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;
}

View File

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

View File

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

View File

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

View File

@@ -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() {

View File

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

View File

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

View File

@@ -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) => {

View File

@@ -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',

View File

@@ -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);
});
});
});

View 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);
}
});

View File

@@ -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);
});

View File

@@ -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 };