mirror of
https://github.com/electron/electron.git
synced 2026-02-26 03:01:17 -05:00
Compare commits
9 Commits
v21.0.0-al
...
v21.0.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76e8536ca8 | ||
|
|
b075434447 | ||
|
|
9673d29ebe | ||
|
|
5e6d152231 | ||
|
|
b31a4b4394 | ||
|
|
28ca18a912 | ||
|
|
8caea9006a | ||
|
|
855536bcb9 | ||
|
|
1978a0b5fe |
@@ -1 +1 @@
|
||||
21.0.0-alpha.3
|
||||
21.0.0-alpha.5
|
||||
@@ -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.
|
||||
|
||||
@@ -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-alpha.3",
|
||||
"version": "21.0.0-alpha.5",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -116,3 +116,4 @@ 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
|
||||
|
||||
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);
|
||||
@@ -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,3
|
||||
PRODUCTVERSION 21,0,0,3
|
||||
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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
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