Compare commits

..

39 Commits

Author SHA1 Message Date
Sudowoodo Release Bot
ce2dcdf42f Bump v20.0.0-beta.6 2022-07-07 06:30:34 -07:00
trop[bot]
283ddb684f fix: <datalist> bounds vertical cutoff (#34786)
fix: DataList bounds overflow

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2022-07-05 12:22:35 -04:00
Sudowoodo Release Bot
5100807a6a Bump v20.0.0-beta.5 2022-07-04 06:31:28 -07:00
trop[bot]
9d7b59e37d docs: document the removal of IA32 Linux support (#34806)
* docs: document the removal of IA32 Linux support

Chromium had dropped support for IA32 Linux, so the Chromium
102.0.4999.0 upgrade PR,
https://github.com/electron/electron/pull/33731, had introduced the commit,
389ef0731e,
to drop support for IA32 Linux but the change landed without an addition
to the documentation for the breaking changes, so this PR adds that.

Closes: https://github.com/electron/electron/issues/34783
Refs: https://bugs.chromium.org/p/chromium/issues/detail?id=1194538
Signed-off-by: Darshan Sen <raisinten@gmail.com>

* Update docs/breaking-changes.md

Co-authored-by: Keeley Hammond <vertedinde@electronjs.org>

Co-authored-by: Darshan Sen <raisinten@gmail.com>
Co-authored-by: Jeremy Rose <nornagon@nornagon.net>
Co-authored-by: Keeley Hammond <vertedinde@electronjs.org>
2022-06-30 11:48:24 -07:00
Keeley Hammond
67957aac67 docs: expand tutorial (#34604) (#34797)
* docs: base tutorial update

* more docs

* zzz

* remove unused images

Co-authored-by: Erick Zhao <erick@hotmail.ca>
2022-06-30 10:07:35 -07:00
Sudowoodo Release Bot
e774d4f348 Bump v20.0.0-beta.4 2022-06-30 06:31:12 -07:00
trop[bot]
3e18fba853 fix: resolve symlinks when computing relative asar paths for integrity (#34779)
Co-authored-by: Samuel Attard <sattard@salesforce.com>
2022-06-29 12:54:12 -07:00
trop[bot]
fa1382d980 build: remove appveyor hook to defunct service (#34792)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2022-06-29 19:33:07 +02:00
trop[bot]
8f738edc40 docs: replace broken Windows taskbar images (#34754)
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2022-06-28 10:08:32 +02:00
Sudowoodo Release Bot
5b4577af20 Bump v20.0.0-beta.3 2022-06-27 06:31:23 -07:00
trop[bot]
cf38b9e375 refactor: load webFrame via process._linkedBinding in security-warnings.ts (#34748)
Co-authored-by: Milan Burda <milan.burda@gmail.com>
2022-06-27 14:12:34 +02:00
trop[bot]
bbc84b0e9a fix: WCO respects maximizable/closable/minimizable (#34721)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2022-06-26 10:15:02 +02:00
Sudowoodo Release Bot
c655ed075a Bump v20.0.0-beta.2 2022-06-23 06:30:53 -07:00
trop[bot]
c48e0b8ab6 feat: add panel support for BrowserWindow (#34665)
* feat: add NSPanel support for BrowserWindow

* change header guard to satisfy linter

* change panel wording in browser-window

* Revert "change panel wording in browser-window"

This reverts commit 6f3f80f94a.

* change wording in browser-window

* Update shell/browser/ui/cocoa/electron_native_widget_mac.mm

Co-authored-by: Cheng Zhao <github@zcbenz.com>

* Update shell/browser/ui/cocoa/electron_native_widget_mac.h

Co-authored-by: Cheng Zhao <github@zcbenz.com>

* Changed ScopedDisableResize class to allow for nesting

Co-authored-by: andreiisaila <andreiisaila@microsoft.com>
Co-authored-by: isailaandrei <isailaandrei.i@gmail.com>
Co-authored-by: Cheng Zhao <github@zcbenz.com>
2022-06-22 15:42:28 -04:00
trop[bot]
46d609fc2c fix: window button visibility fullscreen interaction (#34675)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2022-06-22 12:27:22 +02:00
trop[bot]
03fe3c42d4 chore: move main process only accessible API bindings away from common (#34672)
Co-authored-by: Milan Burda <milan.burda@gmail.com>
2022-06-21 13:54:16 -07:00
trop[bot]
952e2905b9 chore: let result: ReturnType<typeof this._callWindowOpenHandler>; (#34669)
Co-authored-by: Milan Burda <milan.burda@gmail.com>
2022-06-21 13:53:49 -07:00
Sudowoodo Release Bot
7619dd51bf Bump v20.0.0-beta.1 2022-06-21 09:11:51 -07:00
trop[bot]
e2b823fb07 fix: overzealous media key listening on Windows (#34647)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2022-06-21 16:35:02 +09:00
trop[bot]
a1dcdc4664 build: mark existing Node.js flakes as dontcare (#34659)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2022-06-21 16:33:52 +09:00
trop[bot]
2b2900a2f1 fix: performance problem in crashReporter.start() on macOS (#34639)
* fix: performance problem in crashReporter.start() on macOS

This change reduces the duration of crashReporter.start() on Intel macOS
from 622 milliseconds to 257 milliseconds!

Backports https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3641386

  posix: Replace DoubleForkAndExec() with ForkAndSpawn()

  The DoubleForkAndExec() function was taking over 622 milliseconds to run
  on macOS 11 (BigSur) on Intel i5-1038NG7. I did some debugging by adding
  some custom traces and found that the fork() syscall is the bottleneck
  here, i.e., the first fork() takes around 359 milliseconds and the
  nested fork() takes around 263 milliseconds. Replacing the nested fork()
  and exec() with posix_spawn() reduces the time consumption to 257
  milliseconds!

  See https://github.com/libuv/libuv/pull/3064 to know why fork() is so
  slow on macOS and why posix_spawn() is a better replacement.

  Another point to note is that even base::LaunchProcess() from Chromium
  calls posix_spawnp() on macOS -
  8f8d82dea0:base/process/launch_mac.cc;l=295-296

  Change-Id: I25c6ee9629a1ae5d0c32b361b56a1ce0b4b0fd26
  Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3641386
  Reviewed-by: Mark Mentovai <mark@chromium.org>
  Commit-Queue: Mark Mentovai <mark@chromium.org>

Fixes: https://github.com/electron/electron/issues/34321
Signed-off-by: Darshan Sen <raisinten@gmail.com>

* Trigger Build

Co-authored-by: Darshan Sen <raisinten@gmail.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2022-06-21 10:40:35 +09:00
trop[bot]
d87bad743c refactor: remove deprecated drag-and-drop code (#34649)
* refactor: remove deprecated drag-and-drop code

* Update shell/browser/ui/drag_util_mac.mm

Co-authored-by: Cheng Zhao <zcbenz@gmail.com>

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
Co-authored-by: Cheng Zhao <zcbenz@gmail.com>
2022-06-20 15:55:17 -04:00
Sudowoodo Release Bot
33670ac928 Bump v20.0.0-alpha.7 2022-06-20 06:31:05 -07:00
trop[bot]
8ecbc11aa2 chore: fix BrowserView painting when origin updated (#34643)
chore: fix View painting when origin updated

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2022-06-20 16:41:42 +09:00
trop[bot]
4ab3b1d624 refactor: use stubs for gdk-pixbuf dependency (#34635)
* refactor: use stubs for gdk-pixbuf dependency

* Adjust build file

* Add includes

* Merge gdk_pixbuf stubs into gtk stubs

* Split pixbuf sigs into own file again

* Add initialization check

* Apply PR feedback

Co-authored-by: Raymond Zhao <raymondzhao@microsoft.com>
Co-authored-by: Raymond Zhao <7199958+rzhao271@users.noreply.github.com>
2022-06-20 16:40:12 +09:00
trop[bot]
bce95c4886 test: add missing page-title-updated event spec for webview (#34544)
Co-authored-by: Milan Burda <milan.burda@gmail.com>
2022-06-20 13:33:33 +09:00
trop[bot]
5b583c8808 feat: add immersive dark mode on windows (#34549)
feat: add immersive dark mode on windows (#33624)

* feat: add immersive dark mode

* fix syntax and add header

* add me

* Update fuses.json5

* fix: redraw title bar on dark mode change

* chore: SetWindowTheme doesn't seem to be needed

* chore: separate out Win 10 dark mode implementation

* final touches

* final touches

* chore: limit Win 10 to >= 20H1 and drop fuse

* fix types

* fix lint

Co-authored-by: Micha Hanselmann <micha.hanselmann@gmail.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>

Co-authored-by: Michaela Laurencin <35157522+mlaurencin@users.noreply.github.com>
Co-authored-by: Micha Hanselmann <micha.hanselmann@gmail.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2022-06-20 13:33:01 +09:00
trop[bot]
c20d6e54c5 fix: draggable regions updating on bounds change (#34612)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2022-06-20 13:30:39 +09:00
trop[bot]
ff7f2a5091 fix: make preload calculation identical between sandbox & non-sandboxed (#34585)
* fix: make preload calculation identical between sandbox & non-sandboxed

* fix test

* split IPCs

* fix ipc constant

* fix unneeded async function

* remove unused preloads blink preferences

* fix typings

Co-authored-by: Jeremy Rose <japthorp@slack-corp.com>
2022-06-20 10:02:49 +09:00
trop[bot]
10062c9a5b chore: warn when using the deprecated new-window event (#34593)
Co-authored-by: Milan Burda <milan.burda@gmail.com>
2022-06-20 10:00:10 +09:00
Sudowoodo Release Bot
82a6e9bd5a Bump v20.0.0-alpha.6 2022-06-16 06:31:00 -07:00
trop[bot]
10131e36f3 fix: crash when setWindowOpenHandler callback throws (#34547)
* fix: crash when setWindowOpenHandler throws

* refactor: throw as process uncaughtException event

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2022-06-15 14:25:41 +09:00
trop[bot]
37078297c6 fix: all files selection logic on linux (#34516)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2022-06-14 12:36:02 -04:00
trop[bot]
48f4ef65ed refactor: move duplicate code to RendererClientBase::ShouldLoadPreload helper (#34520)
Co-authored-by: Milan Burda <milan.burda@gmail.com>
2022-06-13 17:37:28 -04:00
trop[bot]
e174aac845 fix: make navigator.userAgentData non-empty (#34524)
fix: make navigator.userAgentData non-empty (#34481)

Co-authored-by: Jeremy Rose <jeremya@chromium.org>
2022-06-13 16:04:47 -04:00
trop[bot]
dd1a692aba chore: remove unused proxy-window-open.snapshot.txt (#34521)
chore: remove unused spec-main/fixtures/snapshots/proxy-window-open.snapshot.txt

Co-authored-by: Milan Burda <milan.burda@gmail.com>
2022-06-13 16:21:57 +02:00
Sudowoodo Release Bot
aa05c43994 Bump v20.0.0-alpha.5 2022-06-13 06:30:54 -07:00
trop[bot]
16561d08ae fix: update normal bounds prior to minimizing (#34486)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2022-06-13 11:17:42 +02:00
trop[bot]
edc0a47c89 fix: don't require glibc 2.29+ on linux arm64 builds (#34503)
* build: use sid sysroot for linux arm64

* try this

Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2022-06-10 20:55:44 -04:00
103 changed files with 3487 additions and 797 deletions

View File

@@ -23,7 +23,5 @@
"br_spaces": 0
},
"single-h1": false,
"no-inline-html": {
"allowed_elements": ["br"]
}
"no-inline-html": false
}

View File

@@ -87,7 +87,10 @@ if (is_linux) {
# implementation. In future, this file can be extended to contain
# gtk4 stubs to switch gtk version in runtime.
generate_stubs("electron_gtk_stubs") {
sigs = [ "shell/browser/ui/electron_gtk.sigs" ]
sigs = [
"shell/browser/ui/electron_gdk_pixbuf.sigs",
"shell/browser/ui/electron_gtk.sigs",
]
extra_header = "shell/browser/ui/electron_gtk.fragment"
output_name = "electron_gtk_stubs"
public_deps = [ "//ui/gtk:gtk_config" ]
@@ -371,6 +374,7 @@ source_set("electron_lib") {
"//chrome/app/resources:platform_locale_settings",
"//components/autofill/core/common:features",
"//components/certificate_transparency",
"//components/embedder_support:browser_util",
"//components/language/core/browser",
"//components/net_log",
"//components/network_hints/browser",
@@ -726,14 +730,6 @@ source_set("electron_lib") {
sources += get_target_outputs(":electron_fuses")
if (is_win && enable_win_dark_mode_window_ui) {
sources += [
"shell/browser/win/dark_mode.cc",
"shell/browser/win/dark_mode.h",
]
libs += [ "uxtheme.lib" ]
}
if (allow_runtime_configurable_key_storage) {
defines += [ "ALLOW_RUNTIME_CONFIGURABLE_KEY_STORAGE" ]
}

View File

@@ -1 +1 @@
20.0.0-alpha.4
20.0.0-beta.6

View File

@@ -34,16 +34,6 @@ environment:
MOCHA_REPORTER: mocha-multi-reporters
MOCHA_MULTI_REPORTERS: mocha-appveyor-reporter, tap
GOMA_FALLBACK_ON_AUTH_FAILURE: true
notifications:
- provider: Webhook
url: https://electron-mission-control.herokuapp.com/rest/appveyor-hook
method: POST
headers:
x-mission-control-secret:
secure: 90BLVPcqhJPG7d24v0q/RRray6W3wDQ8uVQlQjOHaBWkw1i8FoA1lsjr2C/v1dVok+tS2Pi6KxDctPUkwIb4T27u4RhvmcPzQhVpfwVJAG9oNtq+yKN7vzHfg7k/pojEzVdJpQLzeJGcSrZu7VY39Q==
on_build_success: false
on_build_failure: true
on_build_status_changed: false
build_script:
- ps: >-
if(($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_NAME -split "/")[0] -eq ($env:APPVEYOR_REPO_NAME -split "/")[0]) {

View File

@@ -19,7 +19,6 @@ buildflag_header("buildflags") {
"ENABLE_ELECTRON_EXTENSIONS=$enable_electron_extensions",
"ENABLE_BUILTIN_SPELLCHECKER=$enable_builtin_spellchecker",
"ENABLE_PICTURE_IN_PICTURE=$enable_picture_in_picture",
"ENABLE_WIN_DARK_MODE_WINDOW_UI=$enable_win_dark_mode_window_ui",
"OVERRIDE_LOCATION_PROVIDER=$enable_fake_location_provider",
]
}

View File

@@ -31,7 +31,4 @@ declare_args() {
# Enable Spellchecker support
enable_builtin_spellchecker = true
# Undocumented Windows dark mode API
enable_win_dark_mode_window_ui = false
}

View File

@@ -69,9 +69,6 @@ an issue:
* [Windows Store](tutorial/windows-store-guide.md)
* [Snapcraft](tutorial/snapcraft.md)
* [Updates](tutorial/updates.md)
* [Deploying an Update Server](tutorial/updates.md#deploying-an-update-server)
* [Implementing Updates in Your App](tutorial/updates.md#implementing-updates-in-your-app)
* [Applying Updates](tutorial/updates.md#applying-updates)
* [Getting Support](tutorial/support.md)
## Detailed Tutorials

View File

@@ -425,13 +425,17 @@ Possible values are:
* On Linux, possible types are `desktop`, `dock`, `toolbar`, `splash`,
`notification`.
* On macOS, possible types are `desktop`, `textured`.
* On macOS, possible types are `desktop`, `textured`, `panel`.
* The `textured` type adds metal gradient appearance
(`NSWindowStyleMaskTexturedBackground`).
* The `desktop` type places the window at the desktop background window level
(`kCGDesktopWindowLevel - 1`). Note that desktop window will not receive
focus, keyboard or mouse events, but you can use `globalShortcut` to receive
input sparingly.
* The `panel` type enables the window to float on top of full-screened apps
by adding the `NSWindowStyleMaskNonactivatingPanel` style mask,normally
reserved for NSPanel, at runtime. Also, the window will appear on all
spaces (desktops).
* On Windows, possible type is `toolbar`.
### Instance Events

View File

@@ -36,7 +36,10 @@ requires unsafe mode), so Electron is unable to support this feature on Linux.
## Planned Breaking API Changes (19.0)
None
### Removed: IA32 Linux binaries
This is a result of Chromium 102.0.4999.0 dropping support for IA32 Linux.
This concludes the [removal of support for IA32 Linux](#removed-ia32-linux-support).
## Planned Breaking API Changes (18.0)
@@ -1203,6 +1206,10 @@ not present, then the native module will fail to load on Windows, with an error
message like `Cannot find module`. See the [native module
guide](/docs/tutorial/using-native-node-modules.md) for more.
### Removed: IA32 Linux support
Electron 18 will no longer run on 32-bit Linux systems. See [discontinuing support for 32-bit Linux](https://www.electronjs.org/blog/linux-32bit-support) for more information.
## Breaking API Changes (3.0)
The following list includes the breaking API changes in Electron 3.0.

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello from Electron renderer!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p>👋</p>
<p id="info"></p>
</body>
<script src="./renderer.js"></script>
</html>

View File

@@ -0,0 +1,26 @@
const { app, BrowserWindow } = require('electron');
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
});
win.loadFile('index.html');
};
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});

View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello from Electron renderer!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p>👋</p>
<p id="info"></p>
</body>
<script src="./renderer.js"></script>
</html>

View File

@@ -0,0 +1,30 @@
const { app, BrowserWindow } = require('electron');
const path = require('path');
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
});
win.loadFile('index.html');
};
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});

View File

@@ -0,0 +1,7 @@
const { contextBridge } = require('electron');
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
});

View File

@@ -0,0 +1,2 @@
const information = document.getElementById('info');
information.innerText = `This app is using Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), and Electron (v${versions.electron()})`;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

View File

@@ -1,26 +1,26 @@
# Application Distribution
---
title: 'Application Packaging'
description: 'To distribute your app with Electron, you need to package and rebrand it. To do this, you can either use specialized tooling or manual approaches.'
slug: application-distribution
hide_title: false
---
## Overview
To distribute your app with Electron, you need to package and rebrand it.
To do this, you can either use specialized tooling or manual approaches.
To distribute your app with Electron, you need to package and rebrand it. To do this, you
can either use specialized tooling or manual approaches.
## With tooling
You can use the following tools to distribute your application:
There are a couple tools out there that exist to package and distribute your Electron app.
We recommend using [Electron Forge](https://www.electronforge.io). You can check out
its documentation directly, or refer to the [Packaging and Distribution](./tutorial-5-packaging.md)
part of the Electron tutorial.
* [electron-forge](https://github.com/electron-userland/electron-forge)
* [electron-builder](https://github.com/electron-userland/electron-builder)
* [electron-packager](https://github.com/electron/electron-packager)
## Manual packaging
These tools will take care of all the steps you need to take to end up with a
distributable Electron application, such as bundling your application,
rebranding the executable, and setting the right icons.
If you prefer the manual approach, there are 2 ways to distribute your application:
You can check the example of how to package your app with `electron-forge` in
the [Quick Start guide](quick-start.md#package-and-distribute-your-application).
## Manual distribution
- With prebuilt binaries
- With an app source code archive
### With prebuilt binaries
@@ -29,21 +29,19 @@ binaries](https://github.com/electron/electron/releases). Next, the folder
containing your app should be named `app` and placed in Electron's resources
directory as shown in the following examples.
> *NOTE:* the location of Electron's prebuilt binaries is indicated
:::note
The location of Electron's prebuilt binaries is indicated
with `electron/` in the examples below.
:::
*On macOS:*
```plaintext
```plain title='macOS'
electron/Electron.app/Contents/Resources/app/
├── package.json
├── main.js
└── index.html
```
*On Windows and Linux:*
```plaintext
```plain title='Windows and Linux'
electron/resources/app
├── package.json
├── main.js
@@ -54,7 +52,7 @@ Then execute `Electron.app` on macOS, `electron` on Linux, or `electron.exe`
on Windows, and Electron will start as your app. The `electron` directory
will then be your distribution to deliver to users.
### With an app source code archive
### With an app source code archive (asar)
Instead of shipping your app by copying all of its source files, you can
package your app into an [asar] archive to improve the performance of reading
@@ -65,16 +63,12 @@ To use an `asar` archive to replace the `app` folder, you need to rename the
archive to `app.asar`, and put it under Electron's resources directory like
below, and Electron will then try to read the archive and start from it.
*On macOS:*
```plaintext
```plain title='macOS'
electron/Electron.app/Contents/Resources/
└── app.asar
```
*On Windows and Linux:*
```plaintext
```plain title='Windows'
electron/resources/
└── app.asar
```
@@ -87,47 +81,44 @@ You can find more details on how to use `asar` in the
After bundling your app into Electron, you will want to rebrand Electron
before distributing it to users.
#### macOS
- **Windows:** You can rename `electron.exe` to any name you like, and edit
its icon and other information with tools like [rcedit](https://github.com/electron/rcedit).
- **Linux:** You can rename the `electron` executable to any name you like.
- **macOS:** You can rename `Electron.app` to any name you want, and you also have to rename
the `CFBundleDisplayName`, `CFBundleIdentifier` and `CFBundleName` fields in the
following files:
You can rename `Electron.app` to any name you want, and you also have to rename
the `CFBundleDisplayName`, `CFBundleIdentifier` and `CFBundleName` fields in the
following files:
- `Electron.app/Contents/Info.plist`
- `Electron.app/Contents/Frameworks/Electron Helper.app/Contents/Info.plist`
* `Electron.app/Contents/Info.plist`
* `Electron.app/Contents/Frameworks/Electron Helper.app/Contents/Info.plist`
You can also rename the helper app to avoid showing `Electron Helper` in the
Activity Monitor, but make sure you have renamed the helper app's executable
file's name.
You can also rename the helper app to avoid showing `Electron Helper` in the
Activity Monitor, but make sure you have renamed the helper app's executable
file's name.
The structure of a renamed app would be like:
The structure of a renamed app would be like:
```plaintext
```plain
MyApp.app/Contents
├── Info.plist
├── MacOS/
│   └── MyApp
└── MyApp
└── Frameworks/
└── MyApp Helper.app
├── Info.plist
└── MacOS/
   └── MyApp Helper
└── MyApp Helper
```
#### Windows
:::note
You can rename `electron.exe` to any name you like, and edit its icon and other
information with tools like [rcedit](https://github.com/electron/rcedit).
#### Linux
You can rename the `electron` executable to any name you like.
### Rebranding by rebuilding Electron from source
It is also possible to rebrand Electron by changing the product name and
it is also possible to rebrand Electron by changing the product name and
building it from source. To do this you need to set the build argument
corresponding to the product name (`electron_product_name = "YourProductName"`)
in the `args.gn` file and rebuild.
Keep in mind this is not recommended as setting up the environment to compile
from source is not trivial and takes significant time.
:::
[asar]: https://github.com/electron/asar

View File

@@ -1,14 +1,20 @@
# Code Signing
---
title: 'Code Signing'
description: 'Code signing is a security technology that you use to certify that an app was created by you.'
slug: code-signing
hide_title: false
---
Code signing is a security technology that you use to certify that an app was
created by you.
created by you. You should sign your application so it does not trigger any
operating system security checks.
On macOS the system can detect any change to the app, whether the change is
On macOS, the system can detect any change to the app, whether the change is
introduced accidentally or by malicious code.
On Windows, the system assigns a trust level to your code signing certificate
which if you don't have, or if your trust level is low, will cause security
dialogs to appear when users start using your application. Trust level builds
dialogs to appear when users start using your application. Trust level builds
over time so it's better to start code signing as early as possible.
While it is possible to distribute unsigned apps, it is not recommended. Both
@@ -16,20 +22,19 @@ Windows and macOS will, by default, prevent either the download or the execution
of unsigned applications. Starting with macOS Catalina (version 10.15), users
have to go through multiple manual steps to open unsigned applications.
![macOS Catalina Gatekeeper warning: The app cannot be opened because the
developer cannot be verified](../images/gatekeeper.png)
![macOS Catalina Gatekeeper warning: The app cannot be opened because the developer cannot be verified](../images/gatekeeper.png)
As you can see, users get two options: Move the app straight to the trash or
cancel running it. You don't want your users to see that dialog.
If you are building an Electron app that you intend to package and distribute,
it should be code-signed.
it should be code signed.
# Signing & notarizing macOS builds
## Signing & notarizing macOS builds
Properly preparing macOS applications for release requires two steps: First, the
app needs to be code-signed. Then, the app needs to be uploaded to Apple for a
process called "notarization", where automated systems will further verify that
Properly preparing macOS applications for release requires two steps. First, the
app needs to be code signed. Then, the app needs to be uploaded to Apple for a
process called **notarization**, where automated systems will further verify that
your app isn't doing anything to endanger its users.
To start the process, ensure that you fulfill the requirements for signing and
@@ -42,18 +47,18 @@ notarizing your app:
Electron's ecosystem favors configuration and freedom, so there are multiple
ways to get your application signed and notarized.
## `electron-forge`
### Using Electron Forge
If you're using Electron's favorite build tool, getting your application signed
and notarized requires a few additions to your configuration. [Forge](https://electronforge.io) is a
collection of the official Electron tools, using [`electron-packager`],
[`electron-osx-sign`], and [`electron-notarize`] under the hood.
Let's take a look at an example configuration with all required fields. Not all
of them are required: the tools will be clever enough to automatically find a
suitable `identity`, for instance, but we recommend that you are explicit.
Let's take a look at an example `package.json` configuration with all required fields. Not all of them are
required: the tools will be clever enough to automatically find a suitable `identity`, for instance,
but we recommend that you are explicit.
```json
```json title="package.json" {7}
{
"name": "my-app",
"version": "0.0.1",
@@ -69,7 +74,7 @@ suitable `identity`, for instance, but we recommend that you are explicit.
},
"osxNotarize": {
"appleId": "felix@felix.fun",
"appleIdPassword": "my-apple-id-password",
"appleIdPassword": "my-apple-id-password"
}
}
}
@@ -77,11 +82,11 @@ suitable `identity`, for instance, but we recommend that you are explicit.
}
```
The `plist` file referenced here needs the following macOS-specific entitlements
The `entitlements.plist` file referenced here needs the following macOS-specific entitlements
to assure the Apple security mechanisms that your app is doing these things
without meaning any harm:
```xml
```xml title="entitlements.plist"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
@@ -104,7 +109,7 @@ file](https://github.com/electron/fiddle/blob/master/forge.config.js).
If you plan to access the microphone or camera within your app using Electron's APIs, you'll also
need to add the following entitlements:
```xml
```xml title="entitlements.plist"
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
@@ -113,28 +118,26 @@ need to add the following entitlements:
If these are not present in your app's entitlements when you invoke, for example:
```js
```js title="main.js"
const { systemPreferences } = require('electron')
const microphone = systemPreferences.askForMediaAccess('microphone')
```
Your app may crash. See the Resource Access section in [Hardened Runtime](https://developer.apple.com/documentation/security/hardened_runtime) for more information and entitlements you may need.
## `electron-builder`
### Using Electron Builder
Electron Builder comes with a custom solution for signing your application. You
can find [its documentation here](https://www.electron.build/code-signing).
## `electron-packager`
### Using Electron Packager
If you're not using an integrated build pipeline like Forge or Builder, you
are likely using [`electron-packager`], which includes [`electron-osx-sign`] and
[`electron-notarize`].
If you're using Packager's API, you can pass [in configuration that both signs
and notarizes your
application](https://electron.github.io/electron-packager/main/interfaces/electronpackager.options.html).
and notarizes your application](https://electron.github.io/electron-packager/main/interfaces/electronpackager.options.html).
```js
const packager = require('electron-packager')
@@ -155,11 +158,11 @@ packager({
})
```
The `plist` file referenced here needs the following macOS-specific entitlements
The `entitlements.plist` file referenced here needs the following macOS-specific entitlements
to assure the Apple security mechanisms that your app is doing these things
without meaning any harm:
```xml
```xml title="entitlements.plist"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
@@ -175,11 +178,11 @@ without meaning any harm:
Up until Electron 12, the `com.apple.security.cs.allow-unsigned-executable-memory` entitlement was required
as well. However, it should not be used anymore if it can be avoided.
## Mac App Store
### Signing Mac App Store applications
See the [Mac App Store Guide].
# Signing Windows builds
## Signing Windows builds
Before signing Windows builds, you must do the following:
@@ -190,31 +193,140 @@ Before signing Windows builds, you must do the following:
You can get a code signing certificate from a lot of resellers. Prices vary, so
it may be worth your time to shop around. Popular resellers include:
* [digicert](https://www.digicert.com/code-signing/microsoft-authenticode.htm)
* [Sectigo](https://sectigo.com/ssl-certificates-tls/code-signing)
* Amongst others, please shop around to find one that suits your needs, Google
is your friend 😄
- [digicert](https://www.digicert.com/code-signing/microsoft-authenticode.htm)
- [Sectigo](https://sectigo.com/ssl-certificates-tls/code-signing)
- Amongst others, please shop around to find one that suits your needs! 😄
There are a number of tools for signing your packaged app:
:::caution Keep your certificate password private
Your certificate password should be a **secret**. Do not share it publicly or
commit it to your source code.
:::
* [`electron-winstaller`] will generate an installer for windows and sign it for
you
* [`electron-forge`] can sign installers it generates through the
Squirrel.Windows or MSI targets.
* [`electron-builder`] can sign some of its windows targets
### Using Electron Forge
## Windows Store
Once you have a code signing certificate file (`.pfx`), you can sign
[Squirrel.Windows][maker-squirrel] and [MSI][maker-msi] installers in Electron Forge
with the `certificateFile` and `certificatePassword` fields in their respective
configuration objects.
For example, if you keep your Forge config in your `package.json` file and are
creating a Squirrel.Windows installer:
```json {9-15} title='package.json'
{
"name": "my-app",
"version": "0.0.1",
//...
"config": {
"forge": {
"packagerConfig": {},
"makers": [
{
"name": "@electron-forge/maker-squirrel",
"config": {
"certificateFile": "./cert.pfx",
"certificatePassword": "this-is-a-secret"
}
}
]
}
}
//...
}
```
### Using electron-winstaller (Squirrel.Windows)
[`electron-winstaller`] is a package that can generate Squirrel.Windows installers for your
Electron app. This is the tool used under the hood by Electron Forge's
[Squirrel.Windows Maker][maker-squirrel]. If you're not using Electron Forge and want to use
`electron-winstaller` directly, use the `certificateFile` and `certificatePassword` configuration
options when creating your installer.
```js {10-11}
const electronInstaller = require('electron-winstaller')
// NB: Use this syntax within an async function, Node does not have support for
// top-level await as of Node 12.
try {
await electronInstaller.createWindowsInstaller({
appDirectory: '/tmp/build/my-app-64',
outputDirectory: '/tmp/build/installer64',
authors: 'My App Inc.',
exe: 'myapp.exe',
certificateFile: './cert.pfx',
certificatePassword: 'this-is-a-secret',
})
console.log('It worked!')
} catch (e) {
console.log(`No dice: ${e.message}`)
}
```
For full configuration options, check out the [`electron-winstaller`] repository!
### Using electron-wix-msi (WiX MSI)
[`electron-wix-msi`] is a package that can generate MSI installers for your
Electron app. This is the tool used under the hood by Electron Forge's [MSI Maker][maker-msi].
If you're not using Electron Forge and want to use `electron-wix-msi` directly, use the
`certificateFile` and `certificatePassword` configuration options
or pass in parameters directly to [SignTool.exe] with the `signWithParams` option.
```js {12-13}
import { MSICreator } from 'electron-wix-msi'
// Step 1: Instantiate the MSICreator
const msiCreator = new MSICreator({
appDirectory: '/path/to/built/app',
description: 'My amazing Kitten simulator',
exe: 'kittens',
name: 'Kittens',
manufacturer: 'Kitten Technologies',
version: '1.1.2',
outputDirectory: '/path/to/output/folder',
certificateFile: './cert.pfx',
certificatePassword: 'this-is-a-secret',
})
// Step 2: Create a .wxs template file
const supportBinaries = await msiCreator.create()
// 🆕 Step 2a: optionally sign support binaries if you
// sign you binaries as part of of your packaging script
supportBinaries.forEach(async (binary) => {
// Binaries are the new stub executable and optionally
// the Squirrel auto updater.
await signFile(binary)
})
// Step 3: Compile the template to a .msi file
await msiCreator.compile()
```
For full configuration options, check out the [`electron-wix-msi`] repository!
### Using Electron Builder
Electron Builder comes with a custom solution for signing your application. You
can find [its documentation here](https://www.electron.build/code-signing).
### Signing Windows Store applications
See the [Windows Store Guide].
[Apple Developer Program]: https://developer.apple.com/programs/
[apple developer program]: https://developer.apple.com/programs/
[`electron-builder`]: https://github.com/electron-userland/electron-builder
[`electron-forge`]: https://github.com/electron-userland/electron-forge
[`electron-osx-sign`]: https://github.com/electron-userland/electron-osx-sign
[`electron-packager`]: https://github.com/electron/electron-packager
[`electron-notarize`]: https://github.com/electron/electron-notarize
[`electron-winstaller`]: https://github.com/electron/windows-installer
[Xcode]: https://developer.apple.com/xcode
[`electron-wix-msi`]: https://github.com/felixrieseberg/electron-wix-msi
[xcode]: https://developer.apple.com/xcode
[signing certificates]: https://github.com/electron/electron-osx-sign/wiki/1.-Getting-Started#certificates
[Mac App Store Guide]: mac-app-store-submission-guide.md
[Windows Store Guide]: windows-store-guide.md
[mac app store guide]: ./mac-app-store-submission-guide.md
[windows store guide]: ./windows-store-guide.md
[maker-squirrel]: https://www.electronforge.io/config/makers/squirrel.windows
[maker-msi]: https://www.electronforge.io/config/makers/wix-msi
[signtool.exe]: https://docs.microsoft.com/en-us/dotnet/framework/tools/signtool-exe

View File

@@ -0,0 +1,54 @@
---
title: 'Distribution Overview'
description: 'To distribute your app with Electron, you need to package and rebrand it. To do this, you can either use specialized tooling or manual approaches.'
slug: distribution-overview
hide_title: false
---
Once your app is ready for production, there are a couple steps you need to take before
you can deliver it to your users.
## Packaging
To distribute your app with Electron, you need to package all your resources and assets
into an executable and rebrand it. To do this, you can either use specialized tooling
or do it manually. See the [Application Packaging][application-packaging] tutorial
for more information.
## Code signing
Code signing is a security technology that you use to certify that an app was
created by you. You should sign your application so it does not trigger the
security checks of your user's operating system.
To get started with each operating system's code signing process, please read the
[Code Signing][code-signing] docs.
## Publishing
Once your app is packaged and signed, you can freely distribute your app directly
to users by uploading your installers online.
To reach more users, you can also choose to upload your app to each operating system's
digital distribution platform (i.e. app store). These require another build step aside
from your direct download app. For more information, check out each individual app store guide:
- [Mac App Store][mac-app]
- [Windows Store][windows-store]
- [Snapcraft (Linux)][snapcraft]
## Updating
Electron's auto-updater allows you to deliver application updates to users
without forcing them to manually download new versions of your application.
Check out the [Updating Applications][updates] guide for details on implementing automatic updates
with Electron.
<!-- Link labels -->
[application-packaging]: ./application-distribution.md
[code-signing]: ./code-signing.md
[mac-app]: ./mac-app-store-submission-guide.md
[windows-store]: ./windows-store-guide.md
[snapcraft]: ./snapcraft.md
[updates]: ./updates.md

56
docs/tutorial/examples.md Normal file
View File

@@ -0,0 +1,56 @@
---
title: 'Examples Overview'
description: 'A set of examples for common Electron features'
slug: examples
hide_title: false
---
# Examples Overview
In this section, we have collected a set of guides for common features
that you may want to implement in your Electron application. Each guide
contains a practical example in a minimal, self-contained example app.
The easiest way to run these examples is by downloading [Electron Fiddle][fiddle].
Once Fiddle is installed, you can press on the "Open in Fiddle" button that you
will find below code samples like the following one:
```fiddle docs/fiddles/quick-start
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
for (const type of ['chrome', 'node', 'electron']) {
replaceText(`${type}-version`, process.versions[type])
}
})
```
If there is still something that you do not know how to do, please take a look at the [API][app]
as there is a chance it might be documented just there (and also open an issue requesting the
guide!).
<!-- guide-table-start -->
| Guide | Description |
| :-------------------- | ------------------------------------------------------------------------------------------------------------------- |
| [Message ports] | This guide provides some examples of how you might use MessagePorts in your app to communicate different processes. |
| [Device access] | Learn how to access the device hardware (Bluetooth, USB, Serial). |
| [Keyboard shortcuts] | Configure local and global keyboard shortcuts for your Electron application. |
| [Multithreading] | With Web Workers, it is possible to run JavaScript in OS-level threads |
| [Offscreen rendering] | Offscreen rendering lets you obtain the content of a BrowserWindow in a bitmap, so it can be rendered anywhere. |
| [Spellchecker] | Learn how to use the built-in spellchecker, set languages, etc. |
| [Web embeds] | Discover the different ways to embed third-party web content in your application. |
<!-- guide-table-end -->
## How to...?
You can find the full list of "How to?" in the sidebar. If there is
something that you would like to do that is not documented, please join
our [Discord server][] and let us know!
[discord server]: https://discord.com/invite/electron
[fiddle]: https://www.electronjs.org/fiddle

View File

@@ -1,10 +1,11 @@
# Introduction
---
title: 'Introduction'
description: 'Welcome to the Electron documentation! If this is your first time developing an Electron app, read through this Getting Started section to get familiar with the basics. Otherwise, feel free to explore our guides and API documentation!'
slug: /latest/
hide_title: false
---
Welcome to the Electron documentation! If this is your first time developing
an Electron app, read through this Getting Started section to get familiar with the
basics. Otherwise, feel free to explore our guides and API documentation!
## What is Electron?
# What is Electron?
Electron is a framework for building desktop applications using JavaScript,
HTML, and CSS. By embedding [Chromium][chromium] and [Node.js][node] into its
@@ -12,20 +13,12 @@ binary, Electron allows you to maintain one JavaScript codebase and create
cross-platform apps that work on Windows, macOS, and Linux — no native development
experience required.
## Prerequisites
## Getting started
These docs operate under the assumption that the reader is familiar with both
Node.js and general web development. If you need to get more comfortable with
either of these areas, we recommend the following resources:
* [Getting started with the Web (MDN)][mdn-guide]
* [Introduction to Node.js][node-guide]
Moreover, you'll have a better time understanding how Electron works if you get
acquainted with Chromium's process model. You can get a brief overview of
Chrome architecture with the [Chrome comic][comic], which was released alongside
Chrome's launch back in 2008. Although it's been over a decade since then, the
core principles introduced in the comic remain helpful to understand Electron.
We recommend you to start with the [tutorial], which guides you through the
process of developing an Electron app and distributing it to users.
The [examples] and [API documentation] are also good places to browse around
and discover new things.
## Running examples with Electron Fiddle
@@ -39,21 +32,44 @@ a code block. If you have Fiddle installed, this button will open a
`fiddle.electronjs.org` link that will automatically load the example into Fiddle,
no copy-pasting required.
```fiddle docs/fiddles/quick-start
```
## What is in the docs?
All the official documentation is available from the sidebar. These
are the different categories and what you can expect on each one:
- **Tutorial**: An end-to-end guide on how to create and publish your first Electron
application.
- **Processes in Electron**: In-depth reference on Electron processes and how to work with them.
- **Best Practices**: Important checklists to keep in mind when developing an Electron app.
- **How-To Examples**: Quick references to add features to your Electron app.
- **Development**: Miscellaneous development guides.
- **Distribution**: Learn how to distribute your app to end users.
- **Testing and debugging**: How to debug JavaScript, write tests, and other tools used
to create quality Electron applications.
- **Resources**: Useful links to better understand how the Electron project works
and is organized.
- **Contributing to Electron**: Compiling Electron and making contributions can be daunting.
We try to make it easier in this section.
## Getting help
Are you getting stuck anywhere? Here are a few links to places to look:
* If you need help with developing your app, our [community Discord server][discord]
is a great place to get advice from other Electron app developers.
* If you suspect you're running into a bug with the `electron` package, please check
the [GitHub issue tracker][issue-tracker] to see if any existing issues match your
problem. If not, feel free to fill out our bug report template and submit a new issue.
- If you need help with developing your app, our [community Discord server][discord]
is a great place to get advice from other Electron app developers.
- If you suspect you're running into a bug with the `electron` package, please check
the [GitHub issue tracker][issue-tracker] to see if any existing issues match your
problem. If not, feel free to fill out our bug report template and submit a new issue.
<!-- Links -->
[api documentation]: ../api/app.md
[chromium]: https://www.chromium.org/
[node]: https://nodejs.org/
[mdn-guide]: https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web
[node-guide]: https://nodejs.dev/learn
[comic]: https://www.google.com/googlebooks/chrome/
[discord]: https://discord.com/invite/APGC3k5yaH
[examples]: examples.md
[fiddle]: https://electronjs.org/fiddle
[issue-tracker]: https://github.com/electron/electron/issues
[discord]: https://discord.gg/electronjs
[node]: https://nodejs.org/

View File

@@ -1,10 +1,17 @@
---
title: 'Process Model'
description: 'Electron inherits its multi-process architecture from Chromium, which makes the framework architecturally very similar to a modern web browser. This guide will expand on the concepts applied in the tutorial.'
slug: process-model
hide_title: false
---
# Process Model
Electron inherits its multi-process architecture from Chromium, which makes the framework
architecturally very similar to a modern web browser. In this guide, we'll expound on
the conceptual knowledge of Electron that we applied in the minimal [quick start app][].
architecturally very similar to a modern web browser. This guide will expand on the
concepts applied in the [Tutorial][tutorial].
[quick start app]: ./quick-start.md
[tutorial]: ./tutorial-1-prerequisites.md
## Why not a single process?
@@ -27,10 +34,10 @@ visualizes this model:
![Chrome's multi-process architecture](../images/chrome-processes.png)
Electron applications are structured very similarly. As an app developer, you control
two types of processes: main and renderer. These are analogous to Chrome's own browser
and renderer processes outlined above.
two types of processes: [main](#the-main-process) and [renderer](#the-renderer-process).
These are analogous to Chrome's own browser and renderer processes outlined above.
[Chrome Comic]: https://www.google.com/googlebooks/chrome/
[chrome comic]: https://www.google.com/googlebooks/chrome/
## The main process
@@ -40,7 +47,7 @@ to `require` modules and use all of Node.js APIs.
### Window management
The primary purpose of the main process is to create and manage application windows with the
The main process' primary purpose is to create and manage application windows with the
[`BrowserWindow`][browser-window] module.
Each instance of the `BrowserWindow` class creates an application window that loads
@@ -68,7 +75,7 @@ When a `BrowserWindow` instance is destroyed, its corresponding renderer process
terminated as well.
[browser-window]: ../api/browser-window.md
[web-embed]: ./web-embeds.md
[web-embed]: ../tutorial/web-embeds.md
[web-contents]: ../api/web-contents.md
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter
@@ -90,7 +97,7 @@ app.on('window-all-closed', () => {
```
[app]: ../api/app.md
[quick-start-lifecycle]: ./quick-start.md#manage-your-windows-lifecycle
[quick-start-lifecycle]: ../tutorial/quick-start.md#manage-your-windows-lifecycle
### Native APIs
@@ -105,7 +112,7 @@ For a full list of Electron's main process modules, check out our API documentat
Each Electron app spawns a separate renderer process for each open `BrowserWindow`
(and each web embed). As its name implies, a renderer is responsible for
*rendering* web content. For all intents and purposes, code ran in renderer processes
_rendering_ web content. For all intents and purposes, code ran in renderer processes
should behave according to web standards (insofar as Chromium does, at least).
Therefore, all user interfaces and app functionality within a single browser
@@ -115,18 +122,22 @@ web.
Although explaining every web spec is out of scope for this guide, the bare minimum
to understand is:
* An HTML file is your entry point for the renderer process.
* UI styling is added through Cascading Style Sheets (CSS).
* Executable JavaScript code can be added through `<script>` elements.
- An HTML file is your entry point for the renderer process.
- UI styling is added through Cascading Style Sheets (CSS).
- Executable JavaScript code can be added through `<script>` elements.
Moreover, this also means that the renderer has no direct access to `require`
or other Node.js APIs. In order to directly include NPM modules in the renderer,
you must use the same bundler toolchains (for example, `webpack` or `parcel`) that you
use on the web.
> Note: Renderer processes can be spawned with a full Node.js environment for ease of
> development. Historically, this used to be the default, but this feature was disabled
> for security reasons.
:::warning
Renderer processes can be spawned with a full Node.js environment for ease of
development. Historically, this used to be the default, but this feature was disabled
for security reasons.
:::
At this point, you might be wondering how your renderer process user interfaces
can interact with Node.js and Electron's native desktop functionality if these
@@ -135,8 +146,9 @@ way to import Electron's content scripts.
## Preload scripts
<!-- Note: This guide doesn't take sandboxing into account, which might fundamentally
<!-- Note: This guide doesn't take sandboxing into account, which might fundamentally
change the statements here. -->
Preload scripts contain code that executes in a renderer process before its web content
begins loading. These scripts run within the renderer context, but are granted more
privileges by having access to Node.js APIs.
@@ -149,8 +161,8 @@ const { BrowserWindow } = require('electron')
//...
const win = new BrowserWindow({
webPreferences: {
preload: 'path/to/preload.js'
}
preload: 'path/to/preload.js',
},
})
//...
```
@@ -165,7 +177,7 @@ the [`contextIsolation`][context-isolation] default.
```js title='preload.js'
window.myAPI = {
desktop: true
desktop: true,
}
```
@@ -184,7 +196,7 @@ securely:
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
desktop: true
desktop: true,
})
```
@@ -195,14 +207,15 @@ console.log(window.myAPI)
This feature is incredibly useful for two main purposes:
* By exposing [`ipcRenderer`][ipcRenderer] helpers to the renderer, you can use
- By exposing [`ipcRenderer`][ipcrenderer] helpers to the renderer, you can use
inter-process communication (IPC) to trigger main process tasks from the
renderer (and vice-versa).
* If you're developing an Electron wrapper for an existing web app hosted on a remote
- If you're developing an Electron wrapper for an existing web app hosted on a remote
URL, you can add custom properties onto the renderer's `window` global that can
be used for desktop-only logic on the web client's side.
[window-mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Window
[context-isolation]: ./context-isolation.md
[context-bridge]: ../api/context-bridge.md
[ipcRenderer]: ../api/ipc-renderer.md
[ipcrenderer]: ../api/ipc-renderer.md
[tutorial]: ./tutorial-1-prerequisites.md

View File

@@ -0,0 +1,143 @@
---
title: 'Prerequisites'
description: 'This guide will step you through the process of creating a barebones Hello World app in Electron, similar to electron/electron-quick-start.'
slug: tutorial-prerequisites
hide_title: false
---
:::info Follow along the tutorial
This is **part 1** of the Electron tutorial.
1. **[Prerequisites][prerequisites]**
1. [Building your First App][building your first app]
1. [Using Preload Scripts][preload]
1. [Adding Features][features]
1. [Packaging Your Application][packaging]
1. [Publishing and Updating][updates]
:::
Electron is a framework for building desktop applications using JavaScript,
HTML, and CSS. By embedding [Chromium][chromium] and [Node.js][node] into a
single binary file, Electron allows you to create cross-platform apps that
work on Windows, macOS, and Linux with a single JavaScript codebase.
This tutorial will guide you through the process of developing a desktop
application with Electron and distributing it to end users.
## Assumptions
Electron is a native wrapper layer for web apps and is run in a Node.js environment.
Therefore, this tutorial assumes you are generally familiar with Node and
front-end web development basics. If you need to do some background reading before
continuing, we recommend the following resources:
- [Getting started with the Web (MDN Web Docs)][mdn-guide]
- [Introduction to Node.js][node-guide]
## Required tools
### Code editor
You will need a text editor to write your code. We recommend using [Visual Studio Code],
although you can choose whichever one you prefer.
### Command line
Throughout the tutorial, we will ask you to use various command-line interfaces (CLIs). You can
type these commands into your system's default terminal:
- Windows: Command Prompt or PowerShell
- macOS: Terminal
- Linux: varies depending on distribution (e.g. GNOME Terminal, Konsole)
Most code editors also come with an integrated terminal, which you can also use.
### Git and GitHub
Git is a commonly-used version control system for source code, and GitHub is a collaborative
development platform built on top of it. Although neither is strictly necessary to building
an Electron application, we will use GitHub releases to set up automatic updates later
on in the tutorial. Therefore, we'll require you to:
- [Create a GitHub account](https://github.com/join)
- [Install Git](https://github.com/git-guides/install-git)
If you're unfamiliar with how Git works, we recommend reading GitHub's [Git guides]. You can also
use the [GitHub Desktop] app if you prefer using a visual interface over the command line.
We recommend that you create a local Git repository and publish it to GitHub before starting
the tutorial, and commit your code after every step.
:::info Installing Git via GitHub Desktop
GitHub Desktop will install the latest version of Git on your system if you don't already have
it installed.
:::
### Node.js and npm
To begin developing an Electron app, you need to install the [Node.js][node-download]
runtime and its bundled npm package manager onto your system. We recommend that you
use the latest long-term support (LTS) version.
:::tip
Please install Node.js using pre-built installers for your platform.
You may encounter incompatibility issues with different development tools otherwise.
If you are using macOS, we recommend using a package manager like [Homebrew] or
[nvm] to avoid any directory permission issues.
:::
To check that Node.js was installed correctly, you can use the `-v` flag when
running the `node` and `npm` commands. These should print out the installed
versions.
```sh
$ node -v
v16.14.2
$ npm -v
8.7.0
```
:::caution
Although you need Node.js installed locally to scaffold an Electron project,
Electron **does not use your system's Node.js installation to run its code**. Instead, it
comes bundled with its own Node.js runtime. This means that your end users do not
need to install Node.js themselves as a prerequisite to running your app.
To check which version of Node.js is running in your app, you can access the global
[`process.versions`] variable in the main process or preload script. You can also reference
the list of versions in the [electron/releases] repository.
:::
<!-- Links -->
[chromium]: https://www.chromium.org/
[electron/releases]: https://github.com/electron/releases/blob/master/readme.md#releases
[homebrew]: https://brew.sh/
[mdn-guide]: https://developer.mozilla.org/en-US/docs/Learn/
[node]: https://nodejs.org/
[node-guide]: https://nodejs.dev/learn
[node-download]: https://nodejs.org/en/download/
[nvm]: https://github.com/nvm-sh/nvm
[process-model]: ./process-model.md
[`process.versions`]: https://nodejs.org/api/process.html#processversions
[github]: https://github.com/
[git guides]: https://github.com/git-guides/
[github desktop]: https://desktop.github.com/
[visual studio code]: https://code.visualstudio.com/
<!-- Tutorial links -->
[prerequisites]: tutorial-1-prerequisites.md
[building your first app]: tutorial-2-first-app.md
[preload]: tutorial-3-preload.md
[features]: tutorial-4-adding-features.md
[packaging]: tutorial-5-packaging.md
[updates]: tutorial-6-publishing-updating.md

View File

@@ -0,0 +1,480 @@
---
title: 'Building your First App'
description: 'This guide will step you through the process of creating a barebones Hello World app in Electron, similar to electron/electron-quick-start.'
slug: tutorial-first-app
hide_title: false
---
:::info Follow along the tutorial
This is **part 2** of the Electron tutorial.
1. [Prerequisites][prerequisites]
1. **[Building your First App][building your first app]**
1. [Using Preload Scripts][preload]
1. [Adding Features][features]
1. [Packaging Your Application][packaging]
1. [Publishing and Updating][updates]
:::
## Learning goals
In this part of the tutorial, you will learn how to set up your Electron project
and write a minimal starter application. By the end of this section,
you should be able to run a working Electron app in development mode from
your terminal.
## Setting up your project
:::caution Avoid WSL
If you are on a Windows machine, please do not use [Windows Subsystem for Linux][wsl] (WSL)
when following this tutorial as you will run into issues when trying to execute the
application.
<!--https://www.electronforge.io/guides/developing-with-wsl-->
:::
### Initializing your npm project
Electron apps are scaffolded using npm, with the package.json file
as an entry point. Start by creating a folder and initializing an npm package
within it with `npm init`.
```sh npm2yarn
mkdir my-electron-app && cd my-electron-app
npm init
```
This command will prompt you to configure some fields in your package.json.
There are a few rules to follow for the purposes of this tutorial:
- _entry point_ should be `main.js` (you will be creating that file soon).
- _author_, _license_, and _description_ can be any value, but will be necessary for
[packaging][packaging] later on.
Then, install Electron into your app's **devDependencies**, which is the list of external
development-only package dependencies not required in production.
:::info Why is Electron a devDependency?
This may seem counter-intuitive since your production code is running Electron APIs.
However, packaged apps will come bundled with the Electron binary, eliminating the need to specify
it as a production dependency.
:::
```sh npm2yarn
npm install electron --save-dev
```
Your package.json file should look something like this after initializing your package
and installing Electron. You should also now have a `node_modules` folder containing
the Electron executable, as well as a `package-lock.json` lockfile that specifies
the exact dependency versions to install.
```json title='package.json'
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Hello World!",
"main": "main.js",
"author": "Jane Doe",
"license": "MIT",
"devDependencies": {
"electron": "19.0.0"
}
}
```
:::info Advanced Electron installation steps
If installing Electron directly fails, please refer to our [Advanced Installation][installation]
documentation for instructions on download mirrors, proxies, and troubleshooting steps.
:::
### Adding a .gitignore
The [`.gitignore`][gitignore] file specifies which files and directories to avoid tracking
with Git. You should place a copy of [GitHub's Node.js gitignore template][gitignore-template]
into your project's root folder to avoid committing your project's `node_modules` folder.
## Running an Electron app
:::tip Further reading
Read [Electron's process model][process-model] documentation to better
understand how Electron's multiple processes work together.
:::
The [`main`][package-json-main] script you defined in package.json is the entry point of any
Electron application. This script controls the **main process**, which runs in a Node.js
environment and is responsible for controlling your app's lifecycle, displaying native
interfaces, performing privileged operations, and managing renderer processes
(more on that later).
Before creating your first Electron app, you will first use a trivial script to ensure your
main process entry point is configured correctly. Create a `main.js` file in the root folder
of your project with a single line of code:
```js title='main.js'
console.log(`Hello from Electron 👋`)
```
Because Electron's main process is a Node.js runtime, you can execute arbitrary Node.js code
with the `electron` command (you can even use it as a [REPL]). To execute this script,
add `electron .` to the `start` command in the [`scripts`][package-scripts]
field of your package.json. This command will tell the Electron executable to look for the main
script in the current directory and run it in dev mode.
```json {8-10} title='package.json'
{
"name": "my-electron-app",
"version": "1.0.0",
"description": "Hello World!",
"main": "main.js",
"author": "Jane Doe",
"license": "MIT",
"scripts": {
"start": "electron ."
},
"devDependencies": {
"electron": "^19.0.0"
}
}
```
```sh npm2yarn
npm run start
```
Your terminal should print out `Hello from Electron 👋`. Congratulations,
you have executed your first line of code in Electron! Next, you will learn
how to create user interfaces with HTML and load that into a native window.
## Loading a web page into a BrowserWindow
In Electron, each window displays a web page that can be loaded either from a local HTML
file or a remote web address. For this example, you will be loading in a local file. Start
by creating a barebones web page in an `index.html` file in the root folder of your project:
```html title='index.html'
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello from Electron renderer!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p>👋</p>
</body>
</html>
```
Now that you have a web page, you can load it into an Electron [BrowserWindow][browser-window].
Replace the contents your `main.js` file with the following code. We will explain each
highlighted block separately.
```js {1,3-10,12-14} title='main.js' showLineNumbers
const { app, BrowserWindow } = require('electron')
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
})
```
### Importing modules
```js title='main.js (Line 1)'
const { app, BrowserWindow } = require('electron')
```
In the first line, we are importing two Electron modules
with CommonJS module syntax:
- [app][app], which controls your application's event lifecycle.
- [BrowserWindow][browser-window], which creates and manages app windows.
:::info Capitalization conventions
You might have noticed the capitalization difference between the **a**pp
and **B**rowser**W**indow modules. Electron follows typical JavaScript conventions here,
where PascalCase modules are instantiable class constructors (e.g. BrowserWindow, Tray,
Notification) whereas camelCase modules are not instantiable (e.g. app, ipcRenderer, webContents).
:::
:::warning ES Modules in Electron
[ECMAScript modules](https://nodejs.org/api/esm.html) (i.e. using `import` to load a module)
are currently not directly supported in Electron. You can find more information about the
state of ESM in Electron in [electron/electron#21457](https://github.com/electron/electron/issues/21457).
:::
### Writing a reusable function to instantiate windows
The `createWindow()` function loads your web page into a new BrowserWindow instance:
```js title='main.js (Lines 3-10)'
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
})
win.loadFile('index.html')
}
```
### Calling your function when the app is ready
```js title='main.js (Lines 12-14)'
app.whenReady().then(() => {
createWindow()
})
```
Many of Electron's core modules are Node.js [event emitters] that adhere to Node's asynchronous
event-driven architecture. The app module is one of these emitters.
In Electron, BrowserWindows can only be created after the app module's [`ready`][app-ready] event
is fired. You can wait for this event by using the [`app.whenReady()`][app-when-ready] API and
calling `createWindow()` once its promise is fulfilled.
:::info
You typically listen to Node.js events by using an emitter's `.on` function.
```diff
+ app.on('ready').then(() => {
- app.whenReady().then(() => {
createWindow()
})
```
However, Electron exposes `app.whenReady()` as a helper specifically for the `ready` event to
avoid subtle pitfalls with directly listening to that event in particular.
See [electron/electron#21972](https://github.com/electron/electron/pull/21972) for details.
:::
At this point, running your Electron application's `start` command should successfully
open a window that displays your web page!
Each web page your app displays in a window will run in a separate process called a
**renderer** process (or simply _renderer_ for short). Renderer processes have access
to the same JavaScript APIs and tooling you use for typical front-end web
development, such as using [webpack] to bundle and minify your code or [React][react]
to build your user interfaces.
## Managing your app's window lifecycle
Application windows behave differently on each operating system. Rather than
enforce these conventions by default, Electron gives you the choice to implement
them in your app code if you wish to follow them. You can implement basic window
conventions by listening for events emitted by the app and BrowserWindow modules.
:::tip Process-specific control flow
Checking against Node's [`process.platform`][node-platform] variable can help you
to run code conditionally on certain platforms. Note that there are only three
possible platforms that Electron can run in: `win32` (Windows), `linux` (Linux),
and `darwin` (macOS).
:::
### Quit the app when all windows are closed (Windows & Linux)
On Windows and Linux, closing all windows will generally quit an application entirely.
To implement this pattern in your Electron app, listen for the app module's
[`window-all-closed`][window-all-closed] event, and call [`app.quit()`][app-quit]
to exit your app if the user is not on macOS.
```js
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
```
### Open a window if none are open (macOS)
In contrast, macOS apps generally continue running even without any windows open.
Activating the app when no windows are available should open a new one.
To implement this feature, listen for the app module's [`activate`][activate]
event, and call your existing `createWindow()` method if no BrowserWindows are open.
Because windows cannot be created before the `ready` event, you should only listen for
`activate` events after your app is initialized. Do this by only listening for activate
events inside your existing `whenReady()` callback.
```js
app.whenReady().then(() => {
createWindow()
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
```
## Final starter code
```fiddle docs/fiddles/tutorial-first-app
```
## Optional: Debugging from VS Code
If you want to debug your application using VS Code, you have need 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:
```json title='.vscode/launch.json'
{
"version": "0.2.0",
"compounds": [
{
"name": "Main + renderer",
"configurations": ["Main", "Renderer"],
"stopAll": true
}
],
"configurations": [
{
"name": "Renderer",
"port": 9222,
"request": "attach",
"type": "pwa-chrome",
"webRoot": "${workspaceFolder}"
},
{
"name": "Main",
"type": "pwa-node",
"request": "launch",
"cwd": "${workspaceFolder}",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
},
"args": [".", "--remote-debugging-port=9222"],
"outputCapture": "std",
"console": "integratedTerminal"
}
]
}
```
The "Main + renderer" option will appear when you select "Run and Debug"
from the sidebar, allowing you to set breakpoints and inspect all the variables among
other things in both the main and renderer processes.
What we have done in the `launch.json` file is to create 3 configurations:
- `Main` is used to start the main process and also expose port 9222 for remote debugging
(`--remote-debugging-port=9222`). This is the port that we will use to attach the debugger
for the `Renderer`. Because the main process is a Node.js process, the type is set to
`pwa-node` (`pwa-` is the prefix that tells VS Code to use the latest JavaScript debugger).
- `Renderer` is used to debug the renderer process. Because the main process is the one
that creates the process, we have to "attach" to it (`"request": "attach"`) instead of
creating a new one.
The renderer process is a web one, so the debugger we have to use is `pwa-chrome`.
- `Main + renderer` is a [compound task] that executes the previous ones simultaneously.
:::caution
Because we are attaching to a process in `Renderer`, it is possible that the first lines of
your code will be skipped as the debugger will not have had enough time to connect before they are
being executed.
You can work around this by refreshing the page or setting a timeout before executing the code
in development mode.
:::
:::info Further reading
If you want to dig deeper in the debugging area, the following guides provide more information:
- [Application Debugging]
- [DevTools Extensions][devtools extension]
:::
## Summary
Electron applications are set up using npm packages. The Electron executable should be installed
in your project's `devDependencies` and can be run in development mode using a script in your
package.json file.
The executable runs the JavaScript entry point found in the `main` property of your package.json.
This file controls Electron's **main process**, which runs an instance of Node.js and is
responsible for your app's lifecycle, displaying native interfaces, performing privileged operations,
and managing renderer processes.
**Renderer processes** (or renderers for short) are responsible for display graphical content. You can
load a web page into a renderer by pointing it to either a web address or a local HTML file.
Renderers behave very similarly to regular web pages and have access to the same web APIs.
In the next section of the tutorial, we will be learning how to augment the renderer process with
privileged APIs and how to communicate between processes.
<!-- Links -->
[activate]: ../api/app.md#event-activate-macos
[advanced-installation]: installation.md
[app]: ../api/app.md
[app-quit]: ../api/app.md#appquit
[app-ready]: ../api/app.md#event-ready
[app-when-ready]: ../api/app.md#appwhenready
[application debugging]: ./application-debugging.md
[browser-window]: ../api/browser-window.md
[commonjs]: https://nodejs.org/docs/../api/modules.html#modules_modules_commonjs_modules
[compound task]: https://code.visualstudio.com/Docs/editor/tasks#_compound-tasks
[devtools extension]: ./devtools-extension.md
[event emitters]: https://nodejs.org/api/events.html#events
[gitignore]: https://git-scm.com/docs/gitignore
[gitignore-template]: https://github.com/github/gitignore/blob/main/Node.gitignore
[installation]: ./installation.md
[node-platform]: https://nodejs.org/api/process.html#process_process_platform
[package-json-main]: https://docs.npmjs.com/cli/v7/configuring-npm/package-json#main
[package-scripts]: https://docs.npmjs.com/cli/v7/using-npm/scripts
[process-model]: process-model.md
[react]: https://reactjs.org
[repl]: ./repl.md
[sandbox]: ./sandbox.md
[webpack]: https://webpack.js.org
[window-all-closed]: ../api/app.md#event-window-all-closed
[wsl]: https://docs.microsoft.com/en-us/windows/wsl/about#what-is-wsl-2
<!-- Tutorial links -->
[prerequisites]: tutorial-1-prerequisites.md
[building your first app]: tutorial-2-first-app.md
[preload]: tutorial-3-preload.md
[features]: tutorial-4-adding-features.md
[packaging]: tutorial-5-packaging.md
[updates]: tutorial-6-publishing-updating.md

View File

@@ -0,0 +1,271 @@
---
title: 'Using Preload Scripts'
description: 'This guide will step you through the process of creating a barebones Hello World app in Electron, similar to electron/electron-quick-start.'
slug: tutorial-preload
hide_title: false
---
:::info Follow along the tutorial
This is **part 3** of the Electron tutorial.
1. [Prerequisites][prerequisites]
1. [Building your First App][building your first app]
1. **[Using Preload Scripts][preload]**
1. [Adding Features][features]
1. [Packaging Your Application][packaging]
1. [Publishing and Updating][updates]
:::
## Learning goals
In this part of the tutorial, you will learn what a preload script is and how to use one
to securely expose privileged APIs into the renderer process. You will also learn how to
communicate between main and renderer processes with Electron's inter-process
communication (IPC) modules.
## What is a preload script?
Electron's main process is a Node.js environment that has full operating system access.
On top of [Electron modules][modules], you can also access [Node.js built-ins][node-api],
as well as any packages installed via npm. On the other hand, renderer processes run web
pages and do not run Node.js by default for security reasons.
To bridge Electron's different process types together, we will need to use a special script
called a **preload**.
## Augmenting the renderer with a preload script
A BrowserWindow's preload script runs in a context that has access to both the HTML DOM
and a Node.js environment. Preload scripts are injected before a web page loads in the renderer,
similar to a Chrome extension's [content scripts][content-script]. To add features to your renderer
that require privileged access, you can define [global] objects through the
[contextBridge][contextbridge] API.
To demonstrate this concept, you will create a preload script that exposes your app's
versions of Chrome, Node, and Electron into the renderer.
Add a new `preload.js` script that exposes selected properties of Electron's `process.versions`
object to the renderer process in a `versions` global variable.
```js title="preload.js"
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
// we can also expose variables, not just functions
})
```
To attach this script to your renderer process, pass its path to the
`webPreferences.preload` option in the BrowserWindow constructor:
```js {8-10} title="main.js"
const { app, BrowserWindow } = require('electron')
const path = require('path')
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
createWindow()
})
```
:::info
There are two Node.js concepts that are used here:
- The [`__dirname`][dirname] string points to the path of the currently executing script
(in this case, your project's root folder).
- The [`path.join`][path-join] API joins multiple path segments together, creating a
combined path string that works across all platforms.
:::
At this point, the renderer has access to the `versions` global, so let's display that
information in the window. This variable can be accessed via `window.versions` or simply
`versions`. Create a `renderer.js` script that uses the [`document.getElementById`]
DOM API to replace the displayed text for the HTML element with `info` as its `id` property.
```js title="renderer.js"
const information = document.getElementById('info')
information.innerText = `This app is using Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), and Electron (v${versions.electron()})`
```
Then, modify your `index.html` by adding a new element with `info` as its `id` property,
and attach your `renderer.js` script:
```html {18,20} title="index.html"
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<meta
http-equiv="X-Content-Security-Policy"
content="default-src 'self'; script-src 'self'"
/>
<title>Hello from Electron renderer!</title>
</head>
<body>
<h1>Hello from Electron renderer!</h1>
<p>👋</p>
<p id="info"></p>
</body>
<script src="./renderer.js"></script>
</html>
```
After following the above steps, your app should look something like this:
![Electron app showing This app is using Chrome (v102.0.5005.63), Node.js (v16.14.2), and Electron (v19.0.3)](../images/preload-example.png)
And the code should look like this:
```fiddle docs/fiddles/tutorial-preload
```
## Communicating between processes
As we have mentioned above, Electron's main and renderer process have distinct responsibilities
and are not interchangeable. This means it is not possible to access the Node.js APIs directly
from the renderer process, nor the HTML Document Object Model (DOM) from the main process.
The solution for this problem is to use Electron's `ipcMain` and `ipcRenderer` modules for
inter-process communication (IPC). To send a message from your web page to the main process,
you can set up a main process handler with `ipcMain.handle` and
then expose a function that calls `ipcRenderer.invoke` to trigger the handler in your preload script.
To illustrate, we will add a global function to the renderer called `ping()`
that will return a string from the main process.
First, set up the `invoke` call in your preload script:
```js {1,7} title="preload.js"
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
ping: () => ipcRenderer.invoke('ping'),
// we can also expose variables, not just functions
})
```
:::caution IPC security
Notice how we wrap the `ipcRenderer.invoke('ping')` call in a helper function rather
than expose the `ipcRenderer` module directly via context bridge. You **never** want to
directly expose the entire `ipcRenderer` module via preload. This would give your renderer
the ability to send arbitrary IPC messages to the main process, which becomes a powerful
attack vector for malicious code.
:::
Then, set up your `handle` listener in the main process. We do this _before_
loading the HTML file so that the handler is guaranteed to be ready before
you send out the `invoke` call from the renderer.
```js {1,11} title="main.js"
const { ipcMain } = require('electron')
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
})
ipcMain.handle('ping', () => 'pong')
win.loadFile('index.html')
}
```
Once you have the sender and receiver set up, you can now send messages from the renderer
to the main process through the `'ping'` channel you just defined.
```js title='renderer.js'
const func = async () => {
const response = await window.versions.ping()
console.log(response) // prints out 'pong'
}
func()
```
:::info
For more in-depth explanations on using the `ipcRenderer` and `ipcMain` modules,
check out the full [Inter-Process Communication][ipc] guide.
:::
## Summary
A preload script contains code that runs before your web page is loaded into the browser
window. It has access to both DOM APIs and Node.js environment, and is often used to
expose privileged APIs to the renderer via the `contextBridge` API.
Because the main and renderer processes have very different responsibilities, Electron
apps often use the preload script to set up inter-process communication (IPC) interfaces
to pass arbitrary messages between the two kinds of processes.
In the next part of the tutorial, we will be showing you resources on adding more
functionality to your app, then teaching you distributing your app to users.
<!-- Links -->
[advanced-installation]: ./installation.md
[application debugging]: ./application-debugging.md
[app]: ../api/app.md
[app-ready]: ../api/app.md#event-ready
[app-when-ready]: ../api/app.md#appwhenready
[browser-window]: ../api/browser-window.md
[commonjs]: https://nodejs.org/docs/latest/api/modules.html#modules_modules_commonjs_modules
[compound task]: https://code.visualstudio.com/Docs/editor/tasks#_compound-tasks
[content-script]: https://developer.chrome.com/docs/extensions/mv3/content_scripts/
[contextbridge]: ../api/context-bridge.md
[context-isolation]: ./context-isolation.md
[`document.getelementbyid`]: https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById
[devtools-extension]: ./devtools-extension.md
[dirname]: https://nodejs.org/api/modules.html#modules_dirname
[global]: https://developer.mozilla.org/en-US/docs/Glossary/Global_object
[ipc]: ./ipc.md
[mdn-csp]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
[modules]: ../api/app.md
[node-api]: https://nodejs.org/dist/latest/docs/api/
[package-json-main]: https://docs.npmjs.com/cli/v7/configuring-npm/package-json#main
[package-scripts]: https://docs.npmjs.com/cli/v7/using-npm/scripts
[path-join]: https://nodejs.org/api/path.html#path_path_join_paths
[process-model]: ./process-model.md
[react]: https://reactjs.org
[sandbox]: ./sandbox.md
[webpack]: https://webpack.js.org
<!-- Tutorial links -->
[prerequisites]: tutorial-1-prerequisites.md
[building your first app]: tutorial-2-first-app.md
[preload]: tutorial-3-preload.md
[features]: tutorial-4-adding-features.md
[packaging]: tutorial-5-packaging.md
[updates]: tutorial-6-publishing-updating.md

View File

@@ -0,0 +1,77 @@
---
title: 'Adding Features'
description: 'In this step of the tutorial, we will share some resources you should read to add features to your application'
slug: tutorial-adding-features
hide_title: false
---
:::info Follow along the tutorial
This is **part 4** of the Electron tutorial.
1. [Prerequisites][prerequisites]
1. [Building your First App][building your first app]
1. [Using Preload Scripts][preload]
1. **[Adding Features][features]**
1. [Packaging Your Application][packaging]
1. [Publishing and Updating][updates]
:::
## Adding application complexity
If you have been following along, you should have a functional Electron application
with a static user interface. From this starting point, you can generally progress
in developing your app in two broad directions:
1. Adding complexity to your renderer process' web app code
1. Deeper integrations with the operating system and Node.js
It is important to understand the distinction between these two broad concepts. For the
first point, Electron-specific resources are not necessary. Building a pretty to-do
list in Electron is just pointing your Electron BrowserWindow to a pretty
to-do list web app. Ultimately, you are building your renderer's UI using the same tools
(HTML, CSS, JavaScript) that you would on the web. Therefore, Electron's docs will
not go in-depth on how to use standard web tools.
On the other hand, Electron also provides a rich set of tools that allow
you to integrate with the desktop environment, from creating tray icons to adding
global shortcuts to displaying native menus. It also gives you all the power of a
Node.js environment in the main process. This set of capabilities separates
Electron applications from running a website in a browser tab, and are the
focus of Electron's documentation.
## How-to examples
Electron's documentation has many tutorials to help you with more advanced topics
and deeper operating system integrations. To get started, check out the
[How-To Examples][how-to] doc.
:::note Let us know if something is missing!
If you can't find what you are looking for, please let us know on [GitHub] or in
our [Discord server][discord]!
:::
## What's next?
For the rest of the tutorial, we will be shifting away from application code
and giving you a look at how you can get your app from your developer machine
into end users' hands.
<!-- Link labels -->
[discord]: https://discord.com/invite/APGC3k5yaH
[github]: https://github.com/electron/electronjs.org-new/issues/new
[how to]: ./examples.md
[node-platform]: https://nodejs.org/api/process.html#process_process_platform
<!-- Tutorial links -->
[prerequisites]: tutorial-1-prerequisites.md
[building your first app]: tutorial-2-first-app.md
[preload]: tutorial-3-preload.md
[features]: tutorial-4-adding-features.md
[packaging]: tutorial-5-packaging.md
[updates]: tutorial-6-publishing-updating.md

View File

@@ -0,0 +1,225 @@
---
title: 'Packaging Your Application'
description: 'To distribute your app with Electron, you need to package it and create installers.'
slug: tutorial-packaging
hide_title: false
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
:::info Follow along the tutorial
This is **part 5** of the Electron tutorial.
1. [Prerequisites][prerequisites]
1. [Building your First App][building your first app]
1. [Using Preload Scripts][preload]
1. [Adding Features][features]
1. **[Packaging Your Application][packaging]**
1. [Publishing and Updating][updates]
:::
## Learning goals
In this part of the tutorial, we'll be going over the basics of packaging and distributing
your app with [Electron Forge].
## Using Electron Forge
Electron does not have any tooling for packaging and distribution bundled into its core
modules. Once you have a working Electron app in dev mode, you need to use
additional tooling to create a packaged app you can distribute to your users (also known
as a **distributable**). Distributables can be either installers (e.g. MSI on Windows) or
portable executable files (e.g. `.app` on macOS).
Electron Forge is an all-in-one tool that handles the packaging and distribution of Electron
apps. Under the hood, it combines a lot of existing Electron tools (e.g. [`electron-packager`],
[`@electron/osx-sign`], [`electron-winstaller`], etc.) into a single interface so you do not
have to worry about wiring them all together.
### Importing your project into Forge
You can install Electron Forge's CLI in your project's `devDependencies` and import your
existing project with a handy conversion script.
```sh npm2yarn
npm install --save-dev @electron-forge/cli
npx electron-forge import
```
Once the conversion script is done, Forge should have added a few scripts
to your `package.json` file.
```json title='package.json'
//...
"scripts": {
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make"
},
//...
```
:::info CLI documentation
For more information on `make` and other Forge APIs, check out
the [Electron Forge CLI documentation].
:::
You should also notice that your package.json now has a few more packages installed
under your `devDependencies`, and contains an added `config.forge` field with an array
of makers configured. **Makers** are Forge plugins that create distributables from
your source code. You should see multiple makers in the pre-populated configuration,
one for each target platform.
### Creating a distributable
To create a distributable, use your project's new `make` script, which runs the
`electron-forge make` command.
```sh npm2yarn
npm run make
```
This `make` command contains two steps:
1. It will first run `electron-forge package` under the hood, which bundles your app
code together with the Electron binary. The packaged code is generated into a folder.
1. It will then use this packaged app folder to create a separate distributable for each
configured maker.
After the script runs, you should see an `out` folder containing both the distributable
and a folder containing the packaged application code.
```plain title='macOS output example'
out/
├── out/make/zip/darwin/x64/my-electron-app-darwin-x64-1.0.0.zip
├── ...
└── out/my-electron-app-darwin-x64/my-electron-app.app/Contents/MacOS/my-electron-app
```
The distributable in the `out/make` folder should be ready to launch! You have now
created your first bundled Electron application.
:::tip Distributable formats
Electron Forge can be configured to create distributables in different OS-specific formats
(e.g. DMG, deb, MSI, etc.). See Forge's [Makers] documentation for all configuration options.
:::
:::note Packaging without Electron Forge
If you want to manually package your code, or if you're just interested understanding the
mechanics behind packaging an Electron app, check out the full [Application Packaging]
documentation.
:::
## Important: signing your code
In order to distribute desktop applications to end users, we _highly recommended_ for you
to **code sign** your Electron app. Code signing is an important part of shipping
desktop applications, and is mandatory for the auto-update step in the final part
of the tutorial.
Code signing is a security technology that you use to certify that a desktop app was
created by a known source. Windows and macOS have their own OS-specific code signing
systems that will make it difficult for users to download or launch unsigned applications.
If you already have code signing certificates for Windows and macOS, you can set your
credentials in your Forge configuration. Otherwise, please refer to the full
[Code Signing] documentation to learn how to purchase a certificate and for more information
on the desktop app code signing process.
On macOS, code signing is done at the app packaging level. On Windows, distributable installers
are signed instead.
<Tabs>
<TabItem value="macos" label="macOS" default>
```json title='package.json' {6-18}
{
//...
"config": {
"forge": {
//...
"packagerConfig": {
"osxSign": {
"identity": "Developer ID Application: Felix Rieseberg (LT94ZKYDCJ)",
"hardened-runtime": true,
"entitlements": "entitlements.plist",
"entitlements-inherit": "entitlements.plist",
"signature-flags": "library"
},
"osxNotarize": {
"appleId": "felix@felix.fun",
"appleIdPassword": "this-is-a-secret"
}
}
//...
}
}
//...
}
```
</TabItem>
<TabItem value="windows" label="Windows">
```json title='package.json' {6-14}
{
//...
"config": {
"forge": {
//...
"makers": [
{
"name": "@electron-forge/maker-squirrel",
"config": {
"certificateFile": "./cert.pfx",
"certificatePassword": "this-is-a-secret"
}
}
]
//...
}
}
//...
}
```
</TabItem>
</Tabs>
## Summary
Electron applications need to be packaged to be distributed to users. In this tutorial,
you imported your app into Electron Forge and configured it to package your app and
generate installers.
In order for your application to be trusted by the user's system, you need to digitally
certify that the distributable is authentic and untampered by code signing it. Your app
can be signed through Forge once you configure it to use your code signing certificate
information.
[`@electron/osx-sign`]: https://github.com/electron/osx-sign
[application packaging]: ./application-distribution.md
[code signing]: ./code-signing.md
[`electron-packager`]: https://github.com/electron/electron-packager
[`electron-winstaller`]: https://github.com/electron/windows-installer
[electron forge]: https://www.electronforge.io
[electron forge cli documentation]: https://www.electronforge.io/cli#commands
[makers]: https://www.electronforge.io/config/makers
<!-- Tutorial links -->
[prerequisites]: tutorial-1-prerequisites.md
[building your first app]: tutorial-2-first-app.md
[preload]: tutorial-3-preload.md
[features]: tutorial-4-adding-features.md
[packaging]: tutorial-5-packaging.md
[updates]: tutorial-6-publishing-updating.md

View File

@@ -0,0 +1,251 @@
---
title: 'Publishing and Updating'
description: "There are several ways to update an Electron application. The easiest and officially supported one is taking advantage of the built-in Squirrel framework and Electron's autoUpdater module."
slug: tutorial-publishing-updating
hide_title: false
---
:::info Follow along the tutorial
This is **part 6** of the Electron tutorial.
1. [Prerequisites][prerequisites]
1. [Building your First App][building your first app]
1. [Using Preload Scripts][preload]
1. [Adding Features][features]
1. [Packaging Your Application][packaging]
1. **[Publishing and Updating][updates]**
:::
## Learning goals
If you've been following along, this is the last step of the tutorial! In this part,
you will publish your app to GitHub releases and integrate automatic updates
into your app code.
## Using update.electronjs.org
The Electron maintainers provide a free auto-updating service for open-source apps
at https://update.electronjs.org. Its requirements are:
- Your app runs on macOS or Windows
- Your app has a public GitHub repository
- Builds are published to [GitHub releases]
- Builds are [code signed][code-signed]
At this point, we'll assume that you have already pushed all your
code to a public GitHub repository.
:::info Alternative update services
If you're using an alternate repository host (e.g. GitLab or Bitbucket) or if
you need to keep your code repository private, please refer to our
[step-by-step guide][update-server] on hosting your own Electron update server.
:::
## Publishing a GitHub release
Electron Forge has [Publisher] plugins that can automate the distribution
of your packaged application to various sources. In this tutorial, we will
be using the GitHub Publisher, which will allow us to publish
our code to GitHub releases.
### Generating a personal access token
Forge cannot publish to any repository on GitHub without permission. You
need to pass in an authenticated token that gives Forge access to
your GitHub releases. The easiest way to do this is to
[create a new personal access token (PAT)][new-pat]
with the `public_repo` scope, which gives write access to your public repositories.
**Make sure to keep this token a secret.**
### Setting up the GitHub Publisher
#### Installing the module
Forge's [GitHub Publisher] is a plugin that
needs to be installed in your project's `devDependencies`:
```sh npm2yarn
npm install --save-dev @electron-forge/publisher-github
```
#### Configuring the publisher in Forge
Once you have it installed, you need to set it up in your Forge
configuration. A full list of options is documented in the Forge's
[`PublisherGitHubConfig`] API docs.
```json title='package.json' {6-16}
{
//...
"config": {
"forge": {
"publishers": [
{
"name": "@electron-forge/publisher-github",
"config": {
"repository": {
"owner": "github-user-name",
"name": "github-repo-name"
},
"prerelease": false,
"draft": true
}
}
]
}
}
//...
}
```
:::tip Drafting releases before publishing
Notice that you have configured Forge to publish your release as a draft.
This will allow you to see the release with its generated artifacts
without actually publishing it to your end users. You can manually
publish your releases via GitHub after writing release notes and
double-checking that your distributables work.
:::
#### Setting up your authentication token
You also need to make the Publisher aware of your authentication token.
By default, it will use the value stored in the `GITHUB_TOKEN` environment
variable.
### Running the publish command
Add Forge's [publish command] to your npm scripts.
```json {6} title='package.json'
//...
"scripts": {
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make",
"publish": "electron-forge publish"
},
//...
```
This command will run your configured makers and publish the output distributables to a new
GitHub release.
```sh npm2yarn
npm run publish
```
By default, this will only publish a single distributable for your host operating system and
architecture. You can publish for different architectures by passing in the `--arch` flag to your
Forge commands.
The name of this release will correspond to the `version` field in your project's package.json file.
:::tip Tagging releases
Optionally, you can also [tag your releases in Git][git-tag] so that your
release is associated with a labeled point in your code history. npm comes
with a handy [`npm version`](https://docs.npmjs.com/cli/v8/commands/npm-version)
command that can handle the version bumping and tagging for you.
:::
#### Bonus: Publishing in GitHub Actions
Publishing locally can be painful, especially because you can only create distributables
for your host operating system (i.e. you can't publish a Window `.exe` file from macOS).
A solution for this would be to publish your app via automation workflows
such as [GitHub Actions], which can run tasks in the
cloud on Ubuntu, macOS, and Windows. This is the exact approach taken by [Electron Fiddle].
You can refer to Fiddle's [Build and Release pipeline][fiddle-build]
and [Forge configuration][fiddle-forge-config]
for more details.
## Instrumenting your updater code
Now that we have a functional release system via GitHub releases, we now need to tell our
Electron app to download an update whenever a new release is out. Electron apps do this
via the [autoUpdater] module, which reads from an update server feed to check if a new version
is available for download.
The update.electronjs.org service provides an updater-compatible feed. For example, Electron
Fiddle v0.28.0 will check the endpoint at https://update.electronjs.org/electron/fiddle/darwin/v0.28.0
to see if a newer GitHub release is available.
After your release is published to GitHub, the update.electronjs.org service should work
for your application. The only step left is to configure the feed with the autoUpdater module.
To make this process easier, the Electron team maintains the [`update-electron-app`] module,
which sets up the autoUpdater boilerplate for update.electronjs.org in one function
call — no configuration required. This module will search for the update.electronjs.org
feed that matches your project's package.json `"repository"` field.
First, install the module as a runtime dependency.
```sh npm2yarn
npm install update-electron-app
```
Then, import the module and call it immediately in the main process.
```js title='main.js'
require('update-electron-app')()
```
And that is all it takes! Once your application is packaged, it will update itself for each new
GitHub release that you publish.
## Summary
In this tutorial, we configured Electron Forge's GitHub Publisher to upload your app's
distributables to GitHub releases. Since distributables cannot always be generated
between platforms, we recommend setting up your building and publishing flow
in a Continuous Integration pipeline if you do not have access to machines.
Electron applications can self-update by pointing the autoUpdater module to an update server feed.
update.electronjs.org is a free update server provided by Electron for open-source applications
published on GitHub releases. Configuring your Electron app to use this service is as easy as
installing and importing the `update-electron-app` module.
If your application is not eligible for update.electronjs.org, you should instead deploy your
own update server and configure the autoUpdater module yourself.
:::info 🌟 You're done!
From here, you have officially completed our tutorial to Electron. Feel free to explore the
rest of our docs and happy developing! If you have questions, please stop by our community
[Discord server].
:::
[autoupdater]: ../api/auto-updater.md
[code-signed]: ./code-signing.md
[discord server]: https://discord.com/invite/APGC3k5yaH
[electron fiddle]: https://electronjs.org/fiddle
[fiddle-build]: https://github.com/electron/fiddle/blob/master/.github/workflows/build.yaml
[fiddle-forge-config]: https://github.com/electron/fiddle/blob/master/forge.config.js
[github actions]: https://github.com/features/actions
[github publisher]: https://www.electronforge.io/config/publishers/github
[github releases]: https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository
[git tag]: https://git-scm.com/book/en/v2/Git-Basics-Tagging
[new-pat]: https://github.com/settings/tokens/new
[publish command]: https://www.electronforge.io/cli#publish
[publisher]: https://www.electronforge.io/config/publishers
[`publishergithubconfig`]: https://js.electronforge.io/publisher/github/interfaces/publishergithubconfig
[`update-electron-app`]: https://github.com/electron/update-electron-app
[update-server]: ./updates.md
<!-- Tutorial links -->
[prerequisites]: tutorial-1-prerequisites.md
[building your first app]: tutorial-2-first-app.md
[preload]: tutorial-3-preload.md
[features]: tutorial-4-adding-features.md
[packaging]: tutorial-5-packaging.md
[updates]: tutorial-6-publishing-updating.md

View File

@@ -1,11 +1,16 @@
# Updating Applications
---
title: 'Updating Applications'
description: "There are several ways to update an Electron application. The easiest and officially supported one is taking advantage of the built-in Squirrel framework and Electron's autoUpdater module."
slug: updates
hide_title: false
---
There are several ways to update an Electron application. The easiest and
officially supported one is taking advantage of the built-in
There are several ways to provide automatic updates to your Electron application.
The easiest and officially supported one is taking advantage of the built-in
[Squirrel](https://github.com/Squirrel) framework and
Electron's [autoUpdater](../api/auto-updater.md) module.
## Using `update.electronjs.org`
## Using update.electronjs.org
The Electron team maintains [update.electronjs.org], a free and open-source
webservice that Electron apps can use to self-update. The service is designed
@@ -13,72 +18,77 @@ for Electron apps that meet the following criteria:
- App runs on macOS or Windows
- App has a public GitHub repository
- Builds are published to GitHub Releases
- Builds are code-signed
- Builds are published to [GitHub Releases][gh-releases]
- Builds are [code-signed](./code-signing.md)
The easiest way to use this service is by installing [update-electron-app],
a Node.js module preconfigured for use with update.electronjs.org.
Install the module:
Install the module using your Node.js package manager of choice:
```sh
```sh npm2yarn
npm install update-electron-app
```
Invoke the updater from your app's main process file:
Then, invoke the updater from your app's main process file:
```js
```js title="main.js"
require('update-electron-app')()
```
By default, this module will check for updates at app startup, then every ten
minutes. When an update is found, it will automatically be downloaded in the background. When the download completes, a dialog is displayed allowing the user
to restart the app.
minutes. When an update is found, it will automatically be downloaded in the background.
When the download completes, a dialog is displayed allowing the user to restart the app.
If you need to customize your configuration, you can
[pass options to `update-electron-app`][update-electron-app]
[pass options to update-electron-app][update-electron-app]
or
[use the update service directly][update.electronjs.org].
## Deploying an Update Server
## Using other update services
If you're developing a private Electron application, or if you're not
publishing releases to GitHub Releases, it may be necessary to run your own
update server.
### Step 1: Deploying an update server
Depending on your needs, you can choose from one of these:
- [Hazel][hazel] Update server for private or open-source apps which can be
deployed for free on [Vercel][vercel]. It pulls from [GitHub Releases][gh-releases]
and leverages the power of GitHub's CDN.
deployed for free on [Vercel][vercel]. It pulls from [GitHub Releases][gh-releases]
and leverages the power of GitHub's CDN.
- [Nuts][nuts] Also uses [GitHub Releases][gh-releases], but caches app
updates on disk and supports private repositories.
updates on disk and supports private repositories.
- [electron-release-server][electron-release-server] Provides a dashboard for
handling releases and does not require releases to originate on GitHub.
handling releases and does not require releases to originate on GitHub.
- [Nucleus][nucleus] A complete update server for Electron apps maintained by
Atlassian. Supports multiple applications and channels; uses a static file store
to minify server cost.
Atlassian. Supports multiple applications and channels; uses a static file store
to minify server cost.
## Implementing Updates in Your App
Once you've deployed your update server, you can instrument your app code to receive and
apply the updates with Electron's [autoUpdater] module.
Once you've deployed your update server, continue with importing the required
modules in your code. The following code might vary for different server
software, but it works like described when using
[Hazel][hazel].
### Step 2: Receiving updates in your app
**Important:** Please ensure that the code below will only be executed in
your packaged app, and not in development. You can use
[electron-is-dev](https://github.com/sindresorhus/electron-is-dev) to check for
the environment.
First, import the required modules in your main process code. The following code might
vary for different server software, but it works like described when using [Hazel][hazel].
```javascript
:::warning Check your execution environment!
Please ensure that the code below will only be executed in your packaged app, and not in development.
You can use the [app.isPackaged](../api/app.md#appispackaged-readonly) API to check the environment.
:::
```javascript title='main.js'
const { app, autoUpdater, dialog } = require('electron')
```
Next, construct the URL of the update server and tell
Next, construct the URL of the update server feed and tell
[autoUpdater](../api/auto-updater.md) about it:
```javascript
```javascript title='main.js'
const server = 'https://your-deployment-url.com'
const url = `${server}/update/${process.platform}/${app.getVersion()}`
@@ -87,32 +97,32 @@ autoUpdater.setFeedURL({ url })
As the final step, check for updates. The example below will check every minute:
```javascript
```javascript title='main.js'
setInterval(() => {
autoUpdater.checkForUpdates()
}, 60000)
```
Once your application is [packaged](../tutorial/application-distribution.md),
Once your application is [packaged](./application-distribution.md),
it will receive an update for each new
[GitHub Release](https://help.github.com/articles/creating-releases/) that you
publish.
## Applying Updates
### Step 3: Notifying users when updates are available
Now that you've configured the basic update mechanism for your application, you
need to ensure that the user will get notified when there's an update. This
can be achieved using the autoUpdater API
[events](../api/auto-updater.md#events):
can be achieved using the [autoUpdater API events](../api/auto-updater.md#events):
```javascript
```javascript title="main.js"
autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => {
const dialogOpts = {
type: 'info',
buttons: ['Restart', 'Later'],
title: 'Application Update',
message: process.platform === 'win32' ? releaseNotes : releaseName,
detail: 'A new version has been downloaded. Restart the application to apply the updates.'
detail:
'A new version has been downloaded. Restart the application to apply the updates.',
}
dialog.showMessageBox(dialogOpts).then((returnValue) => {
@@ -125,16 +135,22 @@ Also make sure that errors are
[being handled](../api/auto-updater.md#event-error). Here's an example
for logging them to `stderr`:
```javascript
autoUpdater.on('error', message => {
```javascript title="main.js"
autoUpdater.on('error', (message) => {
console.error('There was a problem updating the application')
console.error(message)
})
```
## Handling Updates Manually
:::info Handling updates manually
Because the requests made by Auto Update aren't under your direct control, you may find situations that are difficult to handle (such as if the update server is behind authentication). The `url` field does support files, which means that with some effort, you can sidestep the server-communication aspect of the process. [Here's an example of how this could work](https://github.com/electron/electron/issues/5020#issuecomment-477636990).
Because the requests made by autoUpdate aren't under your direct control, you may find situations
that are difficult to handle (such as if the update server is behind authentication). The `url`
field supports the `file://` protocol, which means that with some effort, you can sidestep the
server-communication aspect of the process by loading your update from a local directory.
[Here's an example of how this could work](https://github.com/electron/electron/issues/5020#issuecomment-477636990).
:::
[vercel]: https://vercel.com
[hazel]: https://github.com/vercel/hazel

View File

@@ -41,10 +41,9 @@ as quoted from [MSDN][msdn-jumplist]:
> confuse the user who does not expect that portion of the destination list to
> change.
![IE](https://i-msdn.sec.s-msft.com/dynimg/IC420539.png)
![Taskbar JumpList](../images/windows-taskbar-jumplist.png)
> NOTE: The screenshot above is an example of general tasks of
Internet Explorer
> NOTE: The screenshot above is an example of general tasks for Microsoft Edge
Unlike the dock menu in macOS which is a real menu, user tasks in Windows work
like application shortcuts. For example, when a user clicks a task, the program
@@ -109,7 +108,7 @@ As quoted from [MSDN][msdn-thumbnail]:
> For example, Windows Media Player might offer standard media transport controls
> such as play, pause, mute, and stop.
![player](https://i-msdn.sec.s-msft.com/dynimg/IC420540.png)
![Thumbnail toolbar](../images/windows-taskbar-thumbnail-toolbar.png)
> NOTE: The screenshot above is an example of thumbnail toolbar of Windows
Media Player
@@ -176,7 +175,7 @@ As quoted from [MSDN][msdn-icon-overlay]:
> network status, messenger status, or new mail. The user should not be
> presented with constantly changing overlays or animations.
![Overlay on taskbar button](https://i-msdn.sec.s-msft.com/dynimg/IC420441.png)
![Overlay on taskbar button](../images/windows-taskbar-icon-overlay.png)
> NOTE: The screenshot above is an example of overlay on a taskbar button

View File

@@ -105,6 +105,8 @@ filenames = {
"shell/browser/ui/win/notify_icon.h",
"shell/browser/ui/win/taskbar_host.cc",
"shell/browser/ui/win/taskbar_host.h",
"shell/browser/win/dark_mode.cc",
"shell/browser/win/dark_mode.h",
"shell/browser/win/scoped_hstring.cc",
"shell/browser/win/scoped_hstring.h",
"shell/common/api/electron_api_native_image_win.cc",
@@ -165,6 +167,8 @@ filenames = {
"shell/browser/ui/cocoa/electron_native_widget_mac.mm",
"shell/browser/ui/cocoa/electron_ns_window_delegate.h",
"shell/browser/ui/cocoa/electron_ns_window_delegate.mm",
"shell/browser/ui/cocoa/electron_ns_panel.h",
"shell/browser/ui/cocoa/electron_ns_panel.mm",
"shell/browser/ui/cocoa/electron_ns_window.h",
"shell/browser/ui/cocoa/electron_ns_window.mm",
"shell/browser/ui/cocoa/electron_preview_item.h",

View File

@@ -1,3 +1,3 @@
const { nativeTheme } = process._linkedBinding('electron_common_native_theme');
const { nativeTheme } = process._linkedBinding('electron_browser_native_theme');
module.exports = nativeTheme;

View File

@@ -1,7 +1,7 @@
const {
Notification: ElectronNotification,
isSupported
} = process._linkedBinding('electron_common_notification');
} = process._linkedBinding('electron_browser_notification');
ElectronNotification.isSupported = isSupported;

View File

@@ -1,6 +1,6 @@
import { EventEmitter } from 'events';
const { createScreen } = process._linkedBinding('electron_common_screen');
const { createScreen } = process._linkedBinding('electron_browser_screen');
let _screen: Electron.Screen;

View File

@@ -504,6 +504,7 @@ WebContents.prototype._callWindowOpenHandler = function (event: Electron.Event,
if (!this._windowOpenHandler) {
return defaultResponse;
}
const response = this._windowOpenHandler(details);
if (typeof response !== 'object') {
@@ -666,7 +667,15 @@ WebContents.prototype._init = function () {
postBody,
disposition
};
const result = this._callWindowOpenHandler(event, details);
let result: ReturnType<typeof this._callWindowOpenHandler>;
try {
result = this._callWindowOpenHandler(event, details);
} catch (err) {
event.preventDefault();
throw err;
}
const options = result.browserWindowConstructorOptions;
if (!event.defaultPrevented) {
openGuestWindow({
@@ -697,7 +706,15 @@ WebContents.prototype._init = function () {
referrer,
postBody
};
const result = this._callWindowOpenHandler(event, details);
let result: ReturnType<typeof this._callWindowOpenHandler>;
try {
result = this._callWindowOpenHandler(event, details);
} catch (err) {
event.preventDefault();
throw err;
}
windowOpenOutlivesOpenerOption = result.outlivesOpener;
windowOpenOverriddenOptions = result.browserWindowConstructorOptions;
if (!event.defaultPrevented) {

View File

@@ -5,7 +5,7 @@
* out-of-process (cross-origin) are created here. "Embedder" roughly means
* "parent."
*/
import { BrowserWindow } from 'electron/main';
import { BrowserWindow, deprecate } from 'electron/main';
import type { BrowserWindowConstructorOptions, Referrer, WebContents, LoadURLOptions } from 'electron/main';
import { parseFeatures } from '@electron/internal/browser/parse-features-string';
@@ -155,6 +155,10 @@ function emitDeprecatedNewWindowEvent ({ event, embedder, guest, windowOpenArgs,
...parseContentTypeFormat(postData)
} : null;
if (embedder.listenerCount('new-window') > 0) {
deprecate.log('The new-window event is deprecated and will be removed. Please use contents.setWindowOpenHandler() instead.');
}
embedder.emit(
'new-window',
event,

View File

@@ -68,6 +68,10 @@ ipcMainUtils.handleSync(IPC_MESSAGES.BROWSER_SANDBOX_LOAD, async function (event
};
});
ipcMainUtils.handleSync(IPC_MESSAGES.BROWSER_NONSANDBOX_LOAD, function (event) {
return { preloadPaths: event.sender._getPreloadPaths() };
});
ipcMainInternal.on(IPC_MESSAGES.BROWSER_PRELOAD_ERROR, function (event, preloadPath: string, error: Error) {
event.sender.emit('preload-error', event, preloadPath, error);
});

View File

@@ -3,6 +3,7 @@ export const enum IPC_MESSAGES {
BROWSER_GET_LAST_WEB_PREFERENCES = 'BROWSER_GET_LAST_WEB_PREFERENCES',
BROWSER_PRELOAD_ERROR = 'BROWSER_PRELOAD_ERROR',
BROWSER_SANDBOX_LOAD = 'BROWSER_SANDBOX_LOAD',
BROWSER_NONSANDBOX_LOAD = 'BROWSER_NONSANDBOX_LOAD',
BROWSER_WINDOW_CLOSE = 'BROWSER_WINDOW_CLOSE',
BROWSER_GET_PROCESS_MEMORY_INFO = 'BROWSER_GET_PROCESS_MEMORY_INFO',

View File

@@ -2,6 +2,7 @@ import * as path from 'path';
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
import type * as ipcRendererInternalModule from '@electron/internal/renderer/ipc-renderer-internal';
import type * as ipcRendererUtilsModule from '@electron/internal/renderer/ipc-renderer-internal-utils';
const Module = require('module');
@@ -38,6 +39,7 @@ require('../common/reset-search-paths');
require('@electron/internal/common/init');
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal') as typeof ipcRendererInternalModule;
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils') as typeof ipcRendererUtilsModule;
process.getProcessMemoryInfo = () => {
return ipcRendererInternal.invoke<Electron.ProcessMemoryInfo>(IPC_MESSAGES.BROWSER_GET_PROCESS_MEMORY_INFO);
@@ -48,15 +50,8 @@ const { hasSwitch, getSwitchValue } = process._linkedBinding('electron_common_co
const { mainFrame } = process._linkedBinding('electron_renderer_web_frame');
const nodeIntegration = mainFrame.getWebPreference('nodeIntegration');
const preloadScript = mainFrame.getWebPreference('preload');
const preloadScripts = mainFrame.getWebPreference('preloadScripts');
const appPath = hasSwitch('app-path') ? getSwitchValue('app-path') : null;
// The webContents preload script is loaded after the session preload scripts.
if (preloadScript) {
preloadScripts.push(preloadScript);
}
// Common renderer initialization
require('@electron/internal/renderer/common-init');
@@ -127,8 +122,9 @@ if (nodeIntegration) {
}
}
const { preloadPaths } = ipcRendererUtils.invokeSync(IPC_MESSAGES.BROWSER_NONSANDBOX_LOAD);
// Load the preload scripts.
for (const preloadScript of preloadScripts) {
for (const preloadScript of preloadPaths) {
try {
Module._load(preloadScript);
} catch (error) {

View File

@@ -1,7 +1,8 @@
import { webFrame } from 'electron';
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
const { mainFrame: webFrame } = process._linkedBinding('electron_renderer_web_frame');
let shouldLog: boolean | null = null;
const { platform, execPath, env } = process;

View File

@@ -1,6 +1,6 @@
{
"name": "electron",
"version": "20.0.0-alpha.4",
"version": "20.0.0-beta.6",
"repository": "https://github.com/electron/electron",
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
"devDependencies": {

View File

@@ -111,3 +111,4 @@ make_gtk_getlibgtk_public.patch
build_disable_print_content_analysis.patch
custom_protocols_plzserviceworker.patch
feat_filter_out_non-shareable_windows_in_the_current_application_in.patch
posix_replace_doubleforkandexec_with_forkandspawn.patch

View File

@@ -8,10 +8,10 @@ WebPreferences of in-process child windows, rather than relying on
process-level command line switches, as before.
diff --git a/third_party/blink/common/web_preferences/web_preferences.cc b/third_party/blink/common/web_preferences/web_preferences.cc
index 6d51f6a77c21b60ab756dbf8d4961b351a2e2b07..c6dc3cebfb32489a5e50b850ed02a066bf3f258e 100644
index 6d51f6a77c21b60ab756dbf8d4961b351a2e2b07..26b820b3ce387e01900cec4227ff290113a833c3 100644
--- a/third_party/blink/common/web_preferences/web_preferences.cc
+++ b/third_party/blink/common/web_preferences/web_preferences.cc
@@ -142,6 +142,20 @@ WebPreferences::WebPreferences()
@@ -142,6 +142,19 @@ WebPreferences::WebPreferences()
fake_no_alloc_direct_call_for_testing_enabled(false),
v8_cache_options(blink::mojom::V8CacheOptions::kDefault),
record_whole_document(false),
@@ -20,7 +20,6 @@ index 6d51f6a77c21b60ab756dbf8d4961b351a2e2b07..c6dc3cebfb32489a5e50b850ed02a066
+ is_webview(false),
+ hidden_page(false),
+ offscreen(false),
+ preload(base::FilePath::StringType()),
+ node_integration(false),
+ node_integration_in_worker(false),
+ node_integration_in_sub_frames(false),
@@ -33,21 +32,10 @@ index 6d51f6a77c21b60ab756dbf8d4961b351a2e2b07..c6dc3cebfb32489a5e50b850ed02a066
accelerated_video_decode_enabled(false),
animation_policy(
diff --git a/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc b/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc
index 187fd8d9818693262256d5cbddd5e02659ba903d..f166517f4e70ced310253539fb6197f6b3af559c 100644
index 187fd8d9818693262256d5cbddd5e02659ba903d..28e5178361e03d1ac851fa74214931b2332dd9c2 100644
--- a/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc
+++ b/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc
@@ -23,6 +23,10 @@ bool StructTraits<blink::mojom::WebPreferencesDataView,
!data.ReadCursiveFontFamilyMap(&out->cursive_font_family_map) ||
!data.ReadFantasyFontFamilyMap(&out->fantasy_font_family_map) ||
!data.ReadMathFontFamilyMap(&out->math_font_family_map) ||
+ // Begin Electron-specific WebPreferences.
+ !data.ReadPreloads(&out->preloads) ||
+ !data.ReadPreload(&out->preload) ||
+ // End Electron-specific WebPreferences.
!data.ReadLazyFrameLoadingDistanceThresholdsPx(
&out->lazy_frame_loading_distance_thresholds_px) ||
!data.ReadLazyImageLoadingDistanceThresholdsPx(
@@ -148,6 +152,19 @@ bool StructTraits<blink::mojom::WebPreferencesDataView,
@@ -148,6 +148,19 @@ bool StructTraits<blink::mojom::WebPreferencesDataView,
data.fake_no_alloc_direct_call_for_testing_enabled();
out->v8_cache_options = data.v8_cache_options();
out->record_whole_document = data.record_whole_document();
@@ -68,7 +56,7 @@ index 187fd8d9818693262256d5cbddd5e02659ba903d..f166517f4e70ced310253539fb6197f6
out->accelerated_video_decode_enabled =
data.accelerated_video_decode_enabled();
diff --git a/third_party/blink/public/common/web_preferences/web_preferences.h b/third_party/blink/public/common/web_preferences/web_preferences.h
index 59947bfd3c042e5f7d3993967fece9b519f93472..368ec7fb398409cd6386269934eaffcce356793f 100644
index 59947bfd3c042e5f7d3993967fece9b519f93472..cb2a53f6147767394585ed371744d8a140aace71 100644
--- a/third_party/blink/public/common/web_preferences/web_preferences.h
+++ b/third_party/blink/public/common/web_preferences/web_preferences.h
@@ -10,6 +10,7 @@
@@ -79,17 +67,15 @@ index 59947bfd3c042e5f7d3993967fece9b519f93472..368ec7fb398409cd6386269934eaffcc
#include "net/nqe/effective_connection_type.h"
#include "third_party/blink/public/common/common_export.h"
#include "third_party/blink/public/mojom/css/preferred_color_scheme.mojom-shared.h"
@@ -154,6 +155,22 @@ struct BLINK_COMMON_EXPORT WebPreferences {
@@ -154,6 +155,20 @@ struct BLINK_COMMON_EXPORT WebPreferences {
blink::mojom::V8CacheOptions v8_cache_options;
bool record_whole_document;
+ // Begin Electron-specific WebPreferences.
+ std::vector<base::FilePath> preloads;
+ bool context_isolation;
+ bool is_webview;
+ bool hidden_page;
+ bool offscreen;
+ base::FilePath preload;
+ bool node_integration;
+ bool node_integration_in_worker;
+ bool node_integration_in_sub_frames;
@@ -103,7 +89,7 @@ index 59947bfd3c042e5f7d3993967fece9b519f93472..368ec7fb398409cd6386269934eaffcc
// only controls whether or not the "document.cookie" field is properly
// connected to the backing store, for instance if you wanted to be able to
diff --git a/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h b/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h
index bd425a5869477de9095c6a41c683609d065b08c0..3a5d7450aa40e8a7614e83f316d3d0fb59583381 100644
index bd425a5869477de9095c6a41c683609d065b08c0..c7ba3ff52c9de01028c9e2be214e20cd91cbf309 100644
--- a/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h
+++ b/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h
@@ -6,6 +6,7 @@
@@ -114,15 +100,11 @@ index bd425a5869477de9095c6a41c683609d065b08c0..3a5d7450aa40e8a7614e83f316d3d0fb
#include "mojo/public/cpp/bindings/struct_traits.h"
#include "net/nqe/effective_connection_type.h"
#include "third_party/blink/public/common/common_export.h"
@@ -428,6 +429,60 @@ struct BLINK_COMMON_EXPORT StructTraits<blink::mojom::WebPreferencesDataView,
@@ -428,6 +429,52 @@ struct BLINK_COMMON_EXPORT StructTraits<blink::mojom::WebPreferencesDataView,
return r.record_whole_document;
}
+ // Begin Electron-specific WebPreferences.
+ static const std::vector<base::FilePath>& preloads(const blink::web_pref::WebPreferences& r) {
+ return r.preloads;
+ }
+
+ static bool context_isolation(const blink::web_pref::WebPreferences& r) {
+ return r.context_isolation;
+ }
@@ -139,10 +121,6 @@ index bd425a5869477de9095c6a41c683609d065b08c0..3a5d7450aa40e8a7614e83f316d3d0fb
+ return r.offscreen;
+ }
+
+ static const base::FilePath& preload(const blink::web_pref::WebPreferences& r) {
+ return r.preload;
+ }
+
+ static bool node_integration(const blink::web_pref::WebPreferences& r) {
+ return r.node_integration;
+ }
@@ -176,7 +154,7 @@ index bd425a5869477de9095c6a41c683609d065b08c0..3a5d7450aa40e8a7614e83f316d3d0fb
return r.cookie_enabled;
}
diff --git a/third_party/blink/public/mojom/webpreferences/web_preferences.mojom b/third_party/blink/public/mojom/webpreferences/web_preferences.mojom
index 738ac646e075f7a37ba2a263c7799a755e63d3fb..80d6ae17d34ef5c7fd14e21c4e1ee3ed4ef9c5fe 100644
index 738ac646e075f7a37ba2a263c7799a755e63d3fb..40ab2a4b9a1f9de5f201502adebc166a48fccd35 100644
--- a/third_party/blink/public/mojom/webpreferences/web_preferences.mojom
+++ b/third_party/blink/public/mojom/webpreferences/web_preferences.mojom
@@ -10,6 +10,7 @@ import "third_party/blink/public/mojom/v8_cache_options.mojom";
@@ -187,17 +165,15 @@ index 738ac646e075f7a37ba2a263c7799a755e63d3fb..80d6ae17d34ef5c7fd14e21c4e1ee3ed
enum PointerType {
kPointerNone = 1, // 1 << 0
@@ -206,6 +207,22 @@ struct WebPreferences {
@@ -206,6 +207,20 @@ struct WebPreferences {
V8CacheOptions v8_cache_options;
bool record_whole_document;
+ // Begin Electron-specific WebPreferences.
+ array<mojo_base.mojom.FilePath> preloads;
+ bool context_isolation;
+ bool is_webview;
+ bool hidden_page;
+ bool offscreen;
+ mojo_base.mojom.FilePath preload;
+ bool node_integration;
+ bool node_integration_in_worker;
+ bool node_integration_in_sub_frames;

View File

@@ -59,10 +59,10 @@ index ad366d0fd4c3a637d75a102ab56984f0d01bfc04..d63eb133fd4bab1ea309bb8c742acf88
// true if register successfully, or false if 1) the specificied |accelerator|
// has been registered by another caller or other native applications, or
diff --git a/content/browser/media/media_keys_listener_manager_impl.cc b/content/browser/media/media_keys_listener_manager_impl.cc
index b954f8dde00d4f5257223c464e9145a6bef48900..b58999f295586a61bcc2648488a8b28f15d80a7e 100644
index b954f8dde00d4f5257223c464e9145a6bef48900..ee9da826014d3aae9675daac6cdbc0f447a14efd 100644
--- a/content/browser/media/media_keys_listener_manager_impl.cc
+++ b/content/browser/media/media_keys_listener_manager_impl.cc
@@ -56,7 +56,12 @@ bool MediaKeysListenerManagerImpl::StartWatchingMediaKey(
@@ -56,7 +56,11 @@ bool MediaKeysListenerManagerImpl::StartWatchingMediaKey(
CanActiveMediaSessionControllerReceiveEvents();
// Tell the underlying MediaKeysListener to listen for the key.
@@ -71,12 +71,11 @@ index b954f8dde00d4f5257223c464e9145a6bef48900..b58999f295586a61bcc2648488a8b28f
+#if BUILDFLAG(IS_MAC)
+ !media_key_handling_enabled_ &&
+#endif // BUILDFLAG(IS_MAC)
+ should_start_watching &&
+ media_keys_listener_ &&
+ should_start_watching && media_keys_listener_ &&
!media_keys_listener_->StartWatchingMediaKey(key_code)) {
return false;
}
@@ -239,18 +244,18 @@ void MediaKeysListenerManagerImpl::StartListeningForMediaKeysIfNecessary() {
@@ -239,6 +243,7 @@ void MediaKeysListenerManagerImpl::StartListeningForMediaKeysIfNecessary() {
#endif
if (system_media_controls_) {
@@ -84,19 +83,22 @@ index b954f8dde00d4f5257223c464e9145a6bef48900..b58999f295586a61bcc2648488a8b28f
system_media_controls_->AddObserver(this);
system_media_controls_notifier_ =
std::make_unique<SystemMediaControlsNotifier>(
system_media_controls_.get());
- } else {
- // If we can't access system media controls, then directly listen for media
- // key keypresses instead.
- media_keys_listener_ = ui::MediaKeysListener::Create(
- this, ui::MediaKeysListener::Scope::kGlobal);
- DCHECK(media_keys_listener_);
@@ -251,6 +256,19 @@ void MediaKeysListenerManagerImpl::StartListeningForMediaKeysIfNecessary() {
DCHECK(media_keys_listener_);
}
+ // Directly listen for media key keypresses when using GlobalShortcuts.
+ media_keys_listener_ = ui::MediaKeysListener::Create(
+ this, ui::MediaKeysListener::Scope::kGlobal);
+ DCHECK(media_keys_listener_);
+#if BUILDFLAG(IS_MAC)
+ // Chromium's implementation of SystemMediaControls falls
+ // down into MPRemoteCommandCenter, which makes it such that an app will not
+ // will not receive remote control events until it begins playing audio.
+ // If there's not already a MediaKeysListener instance, create one so
+ // that globalShortcuts work correctly.
+ if (!media_keys_listener_) {
+ media_keys_listener_ = ui::MediaKeysListener::Create(
+ this, ui::MediaKeysListener::Scope::kGlobal);
+ DCHECK(media_keys_listener_);
+ }
+#endif
+
EnsureAuxiliaryServices();
}

View File

@@ -1,16 +1,28 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: deepak1556 <hop2deep@gmail.com>
Date: Thu, 7 Apr 2022 20:30:16 +0900
Subject: Make gtk::GetLibGtk public
Subject: Make gtk::GetLibGtk and gtk::GetLibGdkPixbuf public
Allows embedders to get a handle to the gtk library
already loaded in the process.
Allows embedders to get a handle to the gtk and
gdk_pixbuf libraries already loaded in the process.
diff --git a/ui/gtk/gtk_compat.cc b/ui/gtk/gtk_compat.cc
index b5c7af5bdb93b320f95252d35d2d76bae7f8c445..40b706ed7cde206e98274025148604760b7477f9 100644
index b5c7af5bdb93b320f95252d35d2d76bae7f8c445..65b097cfab72b92f301968715eb218ef0e468567 100644
--- a/ui/gtk/gtk_compat.cc
+++ b/ui/gtk/gtk_compat.cc
@@ -86,12 +86,6 @@ void* GetLibGtk4(bool check = true) {
@@ -66,11 +66,6 @@ void* GetLibGio() {
return libgio;
}
-void* GetLibGdkPixbuf() {
- static void* libgdk_pixbuf = DlOpen("libgdk_pixbuf-2.0.so.0");
- return libgdk_pixbuf;
-}
-
void* GetLibGdk3() {
static void* libgdk3 = DlOpen("libgdk-3.so.0");
return libgdk3;
@@ -86,12 +81,6 @@ void* GetLibGtk4(bool check = true) {
return libgtk4;
}
@@ -23,10 +35,15 @@ index b5c7af5bdb93b320f95252d35d2d76bae7f8c445..40b706ed7cde206e9827402514860476
bool LoadGtk3() {
if (!GetLibGtk3(false))
return false;
@@ -134,6 +128,12 @@ gfx::Insets InsetsFromGtkBorder(const GtkBorder& border) {
@@ -134,6 +123,17 @@ gfx::Insets InsetsFromGtkBorder(const GtkBorder& border) {
} // namespace
+void* GetLibGdkPixbuf() {
+ static void* libgdk_pixbuf = DlOpen("libgdk_pixbuf-2.0.so.0");
+ return libgdk_pixbuf;
+}
+
+void* GetLibGtk() {
+ if (GtkCheckVersion(4))
+ return GetLibGtk4();
@@ -37,13 +54,16 @@ index b5c7af5bdb93b320f95252d35d2d76bae7f8c445..40b706ed7cde206e9827402514860476
static bool loaded = LoadGtkImpl();
return loaded;
diff --git a/ui/gtk/gtk_compat.h b/ui/gtk/gtk_compat.h
index 57e55b9e749b43d327deff449a530e1f435a8e8b..2245974f91be4a691d82f54b55e12e44ae2000c5 100644
index 57e55b9e749b43d327deff449a530e1f435a8e8b..37720be9e393d192b3b7db13a007431a9ce77ddc 100644
--- a/ui/gtk/gtk_compat.h
+++ b/ui/gtk/gtk_compat.h
@@ -34,6 +34,9 @@ using SkColor = uint32_t;
@@ -34,6 +34,12 @@ using SkColor = uint32_t;
namespace gtk {
+// Get handle to the currently loaded gdk_pixbuf library in the process.
+void* GetLibGdkPixbuf();
+
+// Get handle to the currently loaded gtk library in the process.
+void* GetLibGtk();
+

View File

@@ -0,0 +1,641 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Darshan Sen <raisinten@gmail.com>
Date: Fri, 17 Jun 2022 13:19:32 +0530
Subject: posix: Replace DoubleForkAndExec() with ForkAndSpawn()
The DoubleForkAndExec() function was taking over 622 milliseconds to run
on macOS 11 (BigSur) on Intel i5-1038NG7. I did some debugging by adding
some custom traces and found that the fork() syscall is the bottleneck
here, i.e., the first fork() takes around 359 milliseconds and the
nested fork() takes around 263 milliseconds. Replacing the nested fork()
and exec() with posix_spawn() reduces the time consumption to 257
milliseconds!
See https://github.com/libuv/libuv/pull/3064 to know why fork() is so
slow on macOS and why posix_spawn() is a better replacement.
Another point to note is that even base::LaunchProcess() from Chromium
calls posix_spawnp() on macOS -
https://source.chromium.org/chromium/chromium/src/+/8f8d82dea0fa8f11f57c74dbb65126f8daba58f7:base/process/launch_mac.cc;l=295-296
Change-Id: I25c6ee9629a1ae5d0c32b361b56a1ce0b4b0fd26
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3641386
Reviewed-by: Mark Mentovai <mark@chromium.org>
Commit-Queue: Mark Mentovai <mark@chromium.org>
diff --git a/third_party/crashpad/crashpad/AUTHORS b/third_party/crashpad/crashpad/AUTHORS
index 8dcac3238870920d374b86033d05d77ebde351e9..02103924332eddbd158c04f8a395bb4a247e8bd9 100644
--- a/third_party/crashpad/crashpad/AUTHORS
+++ b/third_party/crashpad/crashpad/AUTHORS
@@ -12,3 +12,4 @@ Opera Software ASA
Vewd Software AS
LG Electronics, Inc.
MIPS Technologies, Inc.
+Darshan Sen <raisinten@gmail.com>
diff --git a/third_party/crashpad/crashpad/client/crashpad_client_linux.cc b/third_party/crashpad/crashpad/client/crashpad_client_linux.cc
index 53c2dadac9d6cb74a3c3e07d9917e9162474b8a0..6761db0e8ac5edba6fac90cdf79e38dfd4cd9d70 100644
--- a/third_party/crashpad/crashpad/client/crashpad_client_linux.cc
+++ b/third_party/crashpad/crashpad/client/crashpad_client_linux.cc
@@ -45,7 +45,7 @@
#include "util/linux/socket.h"
#include "util/misc/address_sanitizer.h"
#include "util/misc/from_pointer_cast.h"
-#include "util/posix/double_fork_and_exec.h"
+#include "util/posix/fork_and_spawn.h"
#include "util/posix/scoped_mmap.h"
#include "util/posix/signals.h"
@@ -454,7 +454,7 @@ bool CrashpadClient::StartHandler(
argv.push_back(FormatArgumentInt("initial-client-fd", handler_sock.get()));
argv.push_back("--shared-client-connection");
- if (!DoubleForkAndExec(argv, nullptr, handler_sock.get(), false, nullptr)) {
+ if (!ForkAndSpawn(argv, nullptr, handler_sock.get(), false, nullptr)) {
return false;
}
@@ -609,7 +609,7 @@ bool CrashpadClient::StartJavaHandlerForClient(
int socket) {
std::vector<std::string> argv = BuildAppProcessArgs(
class_name, database, metrics_dir, url, annotations, arguments, socket);
- return DoubleForkAndExec(argv, env, socket, false, nullptr);
+ return ForkAndSpawn(argv, env, socket, false, nullptr);
}
bool CrashpadClient::StartHandlerWithLinkerAtCrash(
@@ -658,7 +658,7 @@ bool CrashpadClient::StartHandlerWithLinkerForClient(
annotations,
arguments,
socket);
- return DoubleForkAndExec(argv, env, socket, false, nullptr);
+ return ForkAndSpawn(argv, env, socket, false, nullptr);
}
#endif
@@ -692,7 +692,7 @@ bool CrashpadClient::StartHandlerForClient(
argv.push_back(FormatArgumentInt("initial-client-fd", socket));
- return DoubleForkAndExec(argv, nullptr, socket, true, nullptr);
+ return ForkAndSpawn(argv, nullptr, socket, true, nullptr);
}
// static
diff --git a/third_party/crashpad/crashpad/client/crashpad_client_mac.cc b/third_party/crashpad/crashpad/client/crashpad_client_mac.cc
index 39e35678ecdd036f8c8ae27c973c27102b77da96..84385f2569f2bd00ca8aed0aa332fb450b2de1d3 100644
--- a/third_party/crashpad/crashpad/client/crashpad_client_mac.cc
+++ b/third_party/crashpad/crashpad/client/crashpad_client_mac.cc
@@ -36,7 +36,7 @@
#include "util/mach/notify_server.h"
#include "util/misc/clock.h"
#include "util/misc/implicit_cast.h"
-#include "util/posix/double_fork_and_exec.h"
+#include "util/posix/fork_and_spawn.h"
namespace crashpad {
@@ -343,7 +343,7 @@ class HandlerStarter final : public NotifyServer::DefaultInterface {
// this parent process, which was probably using the exception server now
// being restarted. The handler cant monitor itself for its own crashes via
// this interface.
- if (!DoubleForkAndExec(
+ if (!ForkAndSpawn(
argv,
nullptr,
server_write_fd.get(),
diff --git a/third_party/crashpad/crashpad/handler/linux/cros_crash_report_exception_handler.cc b/third_party/crashpad/crashpad/handler/linux/cros_crash_report_exception_handler.cc
index 9e58d94aa499fdb7271a78ea21a1dcc1b12e3a52..3caa3b987b35be575558a312026cf6f19485c418 100644
--- a/third_party/crashpad/crashpad/handler/linux/cros_crash_report_exception_handler.cc
+++ b/third_party/crashpad/crashpad/handler/linux/cros_crash_report_exception_handler.cc
@@ -29,7 +29,7 @@
#include "util/linux/ptrace_client.h"
#include "util/misc/metrics.h"
#include "util/misc/uuid.h"
-#include "util/posix/double_fork_and_exec.h"
+#include "util/posix/fork_and_spawn.h"
namespace crashpad {
@@ -266,12 +266,11 @@ bool CrosCrashReportExceptionHandler::HandleExceptionWithConnection(
argv.push_back("--always_allow_feedback");
}
- if (!DoubleForkAndExec(argv,
- nullptr /* envp */,
- file_writer.fd() /* preserve_fd */,
- false /* use_path */,
- nullptr /* child_function */)) {
- LOG(ERROR) << "DoubleForkAndExec failed";
+ if (!ForkAndSpawn(argv,
+ nullptr /* envp */,
+ file_writer.fd() /* preserve_fd */,
+ false /* use_path */,
+ nullptr /* child_function */)) {
Metrics::ExceptionCaptureResult(
Metrics::CaptureResult::kFinishedWritingCrashReportFailed);
return false;
diff --git a/third_party/crashpad/crashpad/util/BUILD.gn b/third_party/crashpad/crashpad/util/BUILD.gn
index 30f03b13f3a018e6a96b0689452a344992675c23..64fa2e9e89b700d8a265a9737cb6a30a210cdffe 100644
--- a/third_party/crashpad/crashpad/util/BUILD.gn
+++ b/third_party/crashpad/crashpad/util/BUILD.gn
@@ -296,10 +296,10 @@ crashpad_static_library("util") {
sources += [
"posix/close_multiple.cc",
"posix/close_multiple.h",
- "posix/double_fork_and_exec.cc",
- "posix/double_fork_and_exec.h",
"posix/drop_privileges.cc",
"posix/drop_privileges.h",
+ "posix/fork_and_spawn.cc",
+ "posix/fork_and_spawn.h",
"posix/process_info.h",
# These map signals to and from strings. While Fuchsia defines some of
diff --git a/third_party/crashpad/crashpad/util/posix/double_fork_and_exec.cc b/third_party/crashpad/crashpad/util/posix/double_fork_and_exec.cc
deleted file mode 100644
index 1960430954d3f6459dce688493db5c42047567b0..0000000000000000000000000000000000000000
--- a/third_party/crashpad/crashpad/util/posix/double_fork_and_exec.cc
+++ /dev/null
@@ -1,166 +0,0 @@
-// Copyright 2017 The Crashpad Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "util/posix/double_fork_and_exec.h"
-
-#include <stdlib.h>
-#include <string.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include "base/check_op.h"
-#include "base/logging.h"
-#include "base/posix/eintr_wrapper.h"
-#include "base/strings/stringprintf.h"
-#include "util/posix/close_multiple.h"
-
-namespace crashpad {
-
-bool DoubleForkAndExec(const std::vector<std::string>& argv,
- const std::vector<std::string>* envp,
- int preserve_fd,
- bool use_path,
- void (*child_function)()) {
- DCHECK(!envp || !use_path);
-
- // argv_c contains const char* pointers and is terminated by nullptr. This is
- // suitable for passing to execv(). Although argv_c is not used in the parent
- // process, it must be built in the parent process because its unsafe to do
- // so in the child or grandchild process.
- std::vector<const char*> argv_c;
- argv_c.reserve(argv.size() + 1);
- for (const std::string& argument : argv) {
- argv_c.push_back(argument.c_str());
- }
- argv_c.push_back(nullptr);
-
- std::vector<const char*> envp_c;
- if (envp) {
- envp_c.reserve(envp->size() + 1);
- for (const std::string& variable : *envp) {
- envp_c.push_back(variable.c_str());
- }
- envp_c.push_back(nullptr);
- }
-
- // Double-fork(). The three processes involved are parent, child, and
- // grandchild. The grandchild will call execv(). The child exits immediately
- // after spawning the grandchild, so the grandchild becomes an orphan and its
- // parent process ID becomes 1. This relieves the parent and child of the
- // responsibility to reap the grandchild with waitpid() or similar. The
- // grandchild is expected to outlive the parent process, so the parent
- // shouldnt be concerned with reaping it. This approach means that accidental
- // early termination of the handler process will not result in a zombie
- // process.
- pid_t pid = fork();
- if (pid < 0) {
- PLOG(ERROR) << "fork";
- return false;
- }
-
- if (pid == 0) {
- // Child process.
-
- if (child_function) {
- child_function();
- }
-
- // Call setsid(), creating a new process group and a new session, both led
- // by this process. The new process group has no controlling terminal. This
- // disconnects it from signals generated by the parent process terminal.
- //
- // setsid() is done in the child instead of the grandchild so that the
- // grandchild will not be a session leader. If it were a session leader, an
- // accidental open() of a terminal device without O_NOCTTY would make that
- // terminal the controlling terminal.
- //
- // Its not desirable for the grandchild to have a controlling terminal. The
- // grandchild manages its own lifetime, such as by monitoring clients on its
- // own and exiting when it loses all clients and when it deems it
- // appropraite to do so. It may serve clients in different process groups or
- // sessions than its original client, and receiving signals intended for its
- // original clients process group could be harmful in that case.
- PCHECK(setsid() != -1) << "setsid";
-
- pid = fork();
- if (pid < 0) {
- PLOG(FATAL) << "fork";
- }
-
- if (pid > 0) {
- // Child process.
-
- // _exit() instead of exit(), because fork() was called.
- _exit(EXIT_SUCCESS);
- }
-
- // Grandchild process.
-
- CloseMultipleNowOrOnExec(STDERR_FILENO + 1, preserve_fd);
-
- // &argv_c[0] is a pointer to a pointer to const char data, but because of
- // how C (not C++) works, execvp() wants a pointer to a const pointer to
- // char data. It modifies neither the data nor the pointers, so the
- // const_cast is safe.
- char* const* argv_for_execv = const_cast<char* const*>(&argv_c[0]);
-
- if (envp) {
- // This cast is safe for the same reason that the argv_for_execv cast is.
- char* const* envp_for_execv = const_cast<char* const*>(&envp_c[0]);
- execve(argv_for_execv[0], argv_for_execv, envp_for_execv);
- PLOG(FATAL) << "execve " << argv_for_execv[0];
- }
-
- if (use_path) {
- execvp(argv_for_execv[0], argv_for_execv);
- PLOG(FATAL) << "execvp " << argv_for_execv[0];
- }
-
- execv(argv_for_execv[0], argv_for_execv);
- PLOG(FATAL) << "execv " << argv_for_execv[0];
- }
-
- // waitpid() for the child, so that it does not become a zombie process. The
- // child normally exits quickly.
- //
- // Failures from this point on may result in the accumulation of a zombie, but
- // should not be considered fatal. Log only warnings, but dont treat these
- // failures as a failure of the function overall.
- int status;
- pid_t wait_pid = HANDLE_EINTR(waitpid(pid, &status, 0));
- if (wait_pid == -1) {
- PLOG(WARNING) << "waitpid";
- return true;
- }
- DCHECK_EQ(wait_pid, pid);
-
- if (WIFSIGNALED(status)) {
- int sig = WTERMSIG(status);
- LOG(WARNING) << base::StringPrintf(
- "intermediate process terminated by signal %d (%s)%s",
- sig,
- strsignal(sig),
- WCOREDUMP(status) ? " (core dumped)" : "");
- } else if (!WIFEXITED(status)) {
- LOG(WARNING) << base::StringPrintf(
- "intermediate process: unknown termination 0x%x", status);
- } else if (WEXITSTATUS(status) != EXIT_SUCCESS) {
- LOG(WARNING) << "intermediate process exited with code "
- << WEXITSTATUS(status);
- }
-
- return true;
-}
-
-} // namespace crashpad
diff --git a/third_party/crashpad/crashpad/util/posix/fork_and_spawn.cc b/third_party/crashpad/crashpad/util/posix/fork_and_spawn.cc
new file mode 100644
index 0000000000000000000000000000000000000000..c6a95bbfdcba45995b0034789c8bdb4423a25642
--- /dev/null
+++ b/third_party/crashpad/crashpad/util/posix/fork_and_spawn.cc
@@ -0,0 +1,235 @@
+// Copyright 2017 The Crashpad Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "util/posix/fork_and_spawn.h"
+
+#include <errno.h>
+#include <spawn.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "base/check.h"
+#include "base/check_op.h"
+#include "base/logging.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/strings/stringprintf.h"
+#include "build/build_config.h"
+#include "util/posix/close_multiple.h"
+
+extern char** environ;
+
+namespace crashpad {
+
+namespace {
+
+#if BUILDFLAG(IS_APPLE)
+
+class PosixSpawnAttr {
+ public:
+ PosixSpawnAttr() {
+ PCHECK((errno = posix_spawnattr_init(&attr_)) == 0)
+ << "posix_spawnattr_init";
+ }
+
+ PosixSpawnAttr(const PosixSpawnAttr&) = delete;
+ PosixSpawnAttr& operator=(const PosixSpawnAttr&) = delete;
+
+ ~PosixSpawnAttr() {
+ PCHECK((errno = posix_spawnattr_destroy(&attr_)) == 0)
+ << "posix_spawnattr_destroy";
+ }
+
+ void SetFlags(short flags) {
+ PCHECK((errno = posix_spawnattr_setflags(&attr_, flags)) == 0)
+ << "posix_spawnattr_setflags";
+ }
+
+ const posix_spawnattr_t* Get() const { return &attr_; }
+
+ private:
+ posix_spawnattr_t attr_;
+};
+
+class PosixSpawnFileActions {
+ public:
+ PosixSpawnFileActions() {
+ PCHECK((errno = posix_spawn_file_actions_init(&file_actions_)) == 0)
+ << "posix_spawn_file_actions_init";
+ }
+
+ PosixSpawnFileActions(const PosixSpawnFileActions&) = delete;
+ PosixSpawnFileActions& operator=(const PosixSpawnFileActions&) = delete;
+
+ ~PosixSpawnFileActions() {
+ PCHECK((errno = posix_spawn_file_actions_destroy(&file_actions_)) == 0)
+ << "posix_spawn_file_actions_destroy";
+ }
+
+ void AddInheritedFileDescriptor(int fd) {
+ PCHECK((errno = posix_spawn_file_actions_addinherit_np(&file_actions_,
+ fd)) == 0)
+ << "posix_spawn_file_actions_addinherit_np";
+ }
+
+ const posix_spawn_file_actions_t* Get() const { return &file_actions_; }
+
+ private:
+ posix_spawn_file_actions_t file_actions_;
+};
+
+#endif
+
+} // namespace
+
+bool ForkAndSpawn(const std::vector<std::string>& argv,
+ const std::vector<std::string>* envp,
+ int preserve_fd,
+ bool use_path,
+ void (*child_function)()) {
+ // argv_c contains const char* pointers and is terminated by nullptr. This is
+ // suitable for passing to posix_spawn() or posix_spawnp(). Although argv_c is
+ // not used in the parent process, it must be built in the parent process
+ // because its unsafe to do so in the child.
+ std::vector<const char*> argv_c;
+ argv_c.reserve(argv.size() + 1);
+ for (const std::string& argument : argv) {
+ argv_c.push_back(argument.c_str());
+ }
+ argv_c.push_back(nullptr);
+
+ std::vector<const char*> envp_c;
+ if (envp) {
+ envp_c.reserve(envp->size() + 1);
+ for (const std::string& variable : *envp) {
+ envp_c.push_back(variable.c_str());
+ }
+ envp_c.push_back(nullptr);
+ }
+
+ // The three processes involved are parent, child, and grandchild. The child
+ // exits immediately after spawning the grandchild, so the grandchild becomes
+ // an orphan and its parent process ID becomes 1. This relieves the parent and
+ // child of the responsibility to reap the grandchild with waitpid() or
+ // similar. The grandchild is expected to outlive the parent process, so the
+ // parent shouldnt be concerned with reaping it. This approach means that
+ // accidental early termination of the handler process will not result in a
+ // zombie process.
+ pid_t pid = fork();
+ if (pid < 0) {
+ PLOG(ERROR) << "fork";
+ return false;
+ }
+
+ if (pid == 0) {
+ // Child process.
+
+ if (child_function) {
+ child_function();
+ }
+
+ // Call setsid(), creating a new process group and a new session, both led
+ // by this process. The new process group has no controlling terminal. This
+ // disconnects it from signals generated by the parent process terminal.
+ //
+ // setsid() is done in the child instead of the grandchild so that the
+ // grandchild will not be a session leader. If it were a session leader, an
+ // accidental open() of a terminal device without O_NOCTTY would make that
+ // terminal the controlling terminal.
+ //
+ // Its not desirable for the grandchild to have a controlling terminal. The
+ // grandchild manages its own lifetime, such as by monitoring clients on its
+ // own and exiting when it loses all clients and when it deems it
+ // appropraite to do so. It may serve clients in different process groups or
+ // sessions than its original client, and receiving signals intended for its
+ // original clients process group could be harmful in that case.
+ PCHECK(setsid() != -1) << "setsid";
+
+ // &argv_c[0] is a pointer to a pointer to const char data, but because of
+ // how C (not C++) works, posix_spawn() and posix_spawnp() want a pointer to
+ // a const pointer to char data. They modifies neither the data nor the
+ // pointers, so the const_cast is safe.
+ char* const* argv_for_spawn = const_cast<char* const*>(argv_c.data());
+
+ // This cast is safe for the same reason that the argv_for_spawn cast is.
+ char* const* envp_for_spawn =
+ envp ? const_cast<char* const*>(envp_c.data()) : environ;
+
+#if BUILDFLAG(IS_APPLE)
+ PosixSpawnAttr attr;
+ attr.SetFlags(POSIX_SPAWN_CLOEXEC_DEFAULT);
+
+ PosixSpawnFileActions file_actions;
+ for (int fd = 0; fd <= STDERR_FILENO; ++fd) {
+ file_actions.AddInheritedFileDescriptor(fd);
+ }
+ file_actions.AddInheritedFileDescriptor(preserve_fd);
+
+ const posix_spawnattr_t* attr_p = attr.Get();
+ const posix_spawn_file_actions_t* file_actions_p = file_actions.Get();
+#else
+ CloseMultipleNowOrOnExec(STDERR_FILENO + 1, preserve_fd);
+
+ const posix_spawnattr_t* attr_p = nullptr;
+ const posix_spawn_file_actions_t* file_actions_p = nullptr;
+#endif
+
+ auto posix_spawn_fp = use_path ? posix_spawnp : posix_spawn;
+ if ((errno = posix_spawn_fp(&pid,
+ argv_for_spawn[0],
+ file_actions_p,
+ attr_p,
+ argv_for_spawn,
+ envp_for_spawn)) != 0) {
+ PLOG(FATAL) << (use_path ? "posix_spawnp" : "posix_spawn");
+ }
+
+ // _exit() instead of exit(), because fork() was called.
+ _exit(EXIT_SUCCESS);
+ }
+
+ // waitpid() for the child, so that it does not become a zombie process. The
+ // child normally exits quickly.
+ //
+ // Failures from this point on may result in the accumulation of a zombie, but
+ // should not be considered fatal. Log only warnings, but dont treat these
+ // failures as a failure of the function overall.
+ int status;
+ pid_t wait_pid = HANDLE_EINTR(waitpid(pid, &status, 0));
+ if (wait_pid == -1) {
+ PLOG(WARNING) << "waitpid";
+ return true;
+ }
+ DCHECK_EQ(wait_pid, pid);
+
+ if (WIFSIGNALED(status)) {
+ int sig = WTERMSIG(status);
+ LOG(WARNING) << base::StringPrintf(
+ "intermediate process terminated by signal %d (%s)%s",
+ sig,
+ strsignal(sig),
+ WCOREDUMP(status) ? " (core dumped)" : "");
+ } else if (!WIFEXITED(status)) {
+ LOG(WARNING) << base::StringPrintf(
+ "intermediate process: unknown termination 0x%x", status);
+ } else if (WEXITSTATUS(status) != EXIT_SUCCESS) {
+ LOG(WARNING) << "intermediate process exited with code "
+ << WEXITSTATUS(status);
+ }
+
+ return true;
+}
+
+} // namespace crashpad
diff --git a/third_party/crashpad/crashpad/util/posix/double_fork_and_exec.h b/third_party/crashpad/crashpad/util/posix/fork_and_spawn.h
similarity index 76%
rename from third_party/crashpad/crashpad/util/posix/double_fork_and_exec.h
rename to third_party/crashpad/crashpad/util/posix/fork_and_spawn.h
index 02fc0f28f196b447132a2dcfaebdaaa5a916a38a..fc55aa3a37652e4ba18c66db90124abd9cad2e51 100644
--- a/third_party/crashpad/crashpad/util/posix/double_fork_and_exec.h
+++ b/third_party/crashpad/crashpad/util/posix/fork_and_spawn.h
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#ifndef CRASHPAD_UTIL_POSIX_DOUBLE_FORK_AND_EXEC_H_
-#define CRASHPAD_UTIL_POSIX_DOUBLE_FORK_AND_EXEC_H_
+#ifndef CRASHPAD_UTIL_POSIX_FORK_AND_SPAWN_H_
+#define CRASHPAD_UTIL_POSIX_FORK_AND_SPAWN_H_
#include <string>
#include <vector>
@@ -23,7 +23,7 @@ namespace crashpad {
//! \brief Executes a (grand-)child process.
//!
//! The grandchild process will be started through the
-//! double-`fork()`-and-`execv()` pattern. This allows the grandchild to fully
+//! `fork()`-and-`posix_spawn()` pattern. This allows the grandchild to fully
//! disassociate from the parent. The grandchild will not be a member of the
//! parents process group or session and will not have a controlling terminal,
//! providing isolation from signals not intended for it. The grandchilds
@@ -37,7 +37,7 @@ namespace crashpad {
//! \param[in] argv The argument vector to start the grandchild process with.
//! `argv[0]` is used as the path to the executable.
//! \param[in] envp A vector of environment variables of the form `var=value` to
-//! be passed to `execve()`. If this value is `nullptr`, the current
+//! be passed to `posix_spawn()`. If this value is `nullptr`, the current
//! environment is used.
//! \param[in] preserve_fd A file descriptor to be inherited by the grandchild
//! process. This file descriptor is inherited in addition to the three file
@@ -45,16 +45,13 @@ namespace crashpad {
//! if no additional file descriptors are to be inherited.
//! \param[in] use_path Whether to consult the `PATH` environment variable when
//! requested to start an executable at a non-absolute path. If `false`,
-//! `execv()`, which does not consult `PATH`, will be used. If `true`,
-//! `execvp()`, which does consult `PATH`, will be used.
+//! `posix_spawn()`, which does not consult `PATH`, will be used. If `true`,
+//! `posix_spawnp()`, which does consult `PATH`, will be used.
//! \param[in] child_function If not `nullptr`, this function will be called in
-//! the intermediate child process, prior to the second `fork()`. Take note
+//! the intermediate child process, prior to the `posix_spawn()`. Take note
//! that this function will run in the context of a forked process, and must
//! be safe for that purpose.
//!
-//! Setting both \a envp to a value other than `nullptr` and \a use_path to
-//! `true` is not currently supported.
-//!
//! \return `true` on success, and `false` on failure with a message logged.
//! Only failures that occur in the parent process that indicate a definite
//! failure to start the the grandchild are reported in the return value.
@@ -63,12 +60,12 @@ namespace crashpad {
//! terminating. The caller assumes the responsibility for detecting such
//! failures, for example, by observing a failure to perform a successful
//! handshake with the grandchild process.
-bool DoubleForkAndExec(const std::vector<std::string>& argv,
- const std::vector<std::string>* envp,
- int preserve_fd,
- bool use_path,
- void (*child_function)());
+bool ForkAndSpawn(const std::vector<std::string>& argv,
+ const std::vector<std::string>* envp,
+ int preserve_fd,
+ bool use_path,
+ void (*child_function)());
} // namespace crashpad
-#endif // CRASHPAD_UTIL_POSIX_DOUBLE_FORK_AND_EXEC_H_
+#endif // CRASHPAD_UTIL_POSIX_FORK_AND_SPAWN_H_

View File

@@ -28,6 +28,7 @@ const defaultOptions = [
'--mode=debug',
'default',
`--skip-tests=${DISABLED_TESTS.join(',')}`,
'--flaky-tests=dontcare',
'--shell',
utils.getAbsoluteElectronExec(),
'-J'

View File

@@ -10,9 +10,9 @@
"Tarball": "debian_bullseye_arm_sysroot.tar.xz"
},
"bullseye_arm64": {
"Sha1Sum": "de38cc85d51a820c3307e1937f3c2d3cedcce988",
"Sha1Sum": "5a56c1ef714154ea5003bcafb16f21b0f8dde023",
"SysrootDir": "debian_bullseye_arm64-sysroot",
"Tarball": "debian_bullseye_arm64_sysroot.tar.xz"
"Tarball": "debian_sid_arm64_sysroot.tar.xz"
},
"bullseye_armel": {
"Sha1Sum": "db15aab39af3cfbc55a8ff0386943db1b78a1eab",

View File

@@ -174,4 +174,4 @@ bool Converter<ui::NativeTheme::ThemeSource>::FromV8(
} // namespace gin
NODE_LINKED_MODULE_CONTEXT_AWARE(electron_common_native_theme, Initialize)
NODE_LINKED_MODULE_CONTEXT_AWARE(electron_browser_native_theme, Initialize)

View File

@@ -304,4 +304,4 @@ void Initialize(v8::Local<v8::Object> exports,
} // namespace
NODE_LINKED_MODULE_CONTEXT_AWARE(electron_common_notification, Initialize)
NODE_LINKED_MODULE_CONTEXT_AWARE(electron_browser_notification, Initialize)

View File

@@ -185,4 +185,4 @@ void Initialize(v8::Local<v8::Object> exports,
} // namespace
NODE_LINKED_MODULE_CONTEXT_AWARE(electron_common_screen, Initialize)
NODE_LINKED_MODULE_CONTEXT_AWARE(electron_browser_screen, Initialize)

View File

@@ -28,6 +28,7 @@
#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
#include "chrome/browser/ui/views/eye_dropper/eye_dropper.h"
#include "chrome/common/pref_names.h"
#include "components/embedder_support/user_agent_utils.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/security_state/content/content_utils.h"
@@ -659,9 +660,10 @@ WebContents::WebContents(v8::Isolate* isolate,
auto session = Session::CreateFrom(isolate, GetBrowserContext());
session_.Reset(isolate, session.ToV8());
web_contents->SetUserAgentOverride(blink::UserAgentOverride::UserAgentOnly(
GetBrowserContext()->GetUserAgent()),
false);
absl::optional<std::string> user_agent_override =
GetBrowserContext()->GetUserAgentOverride();
if (user_agent_override)
SetUserAgent(*user_agent_override);
web_contents->SetUserData(kElectronApiWebContentsKey,
std::make_unique<UserDataLink>(GetWeakPtr()));
InitZoomController(web_contents, gin::Dictionary::CreateEmpty(isolate));
@@ -867,9 +869,10 @@ void WebContents::InitWithSessionAndOptions(
AutofillDriverFactory::CreateForWebContents(web_contents());
web_contents()->SetUserAgentOverride(blink::UserAgentOverride::UserAgentOnly(
GetBrowserContext()->GetUserAgent()),
false);
absl::optional<std::string> user_agent_override =
GetBrowserContext()->GetUserAgentOverride();
if (user_agent_override)
SetUserAgent(*user_agent_override);
if (IsGuest()) {
NativeWindow* owner_window = nullptr;
@@ -2150,8 +2153,7 @@ void WebContents::LoadURL(const GURL& url,
std::string user_agent;
if (options.Get("userAgent", &user_agent))
web_contents()->SetUserAgentOverride(
blink::UserAgentOverride::UserAgentOnly(user_agent), false);
SetUserAgent(user_agent);
std::string extra_headers;
if (options.Get("extraHeaders", &extra_headers))
@@ -2366,8 +2368,12 @@ void WebContents::ForcefullyCrashRenderer() {
}
void WebContents::SetUserAgent(const std::string& user_agent) {
web_contents()->SetUserAgentOverride(
blink::UserAgentOverride::UserAgentOnly(user_agent), false);
blink::UserAgentOverride ua_override;
ua_override.ua_string_override = user_agent;
if (!user_agent.empty())
ua_override.ua_metadata_override = embedder_support::GetUserAgentMetadata();
web_contents()->SetUserAgentOverride(ua_override, false);
}
std::string WebContents::GetUserAgent() {

View File

@@ -30,6 +30,7 @@
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_version.h"
#include "components/embedder_support/user_agent_utils.h"
#include "components/net_log/chrome_net_log.h"
#include "components/network_hints/common/network_hints.mojom.h"
#include "content/browser/keyboard_lock/keyboard_lock_service_impl.h" // nogncheck
@@ -496,11 +497,6 @@ void ElectronBrowserClient::OverrideWebkitPrefs(
? blink::mojom::PreferredColorScheme::kDark
: blink::mojom::PreferredColorScheme::kLight;
auto preloads =
SessionPreferences::GetValidPreloads(web_contents->GetBrowserContext());
if (!preloads.empty())
prefs->preloads = preloads;
SetFontDefaults(prefs);
// Custom preferences of guest page.
@@ -1159,6 +1155,10 @@ void ElectronBrowserClient::SetUserAgent(const std::string& user_agent) {
user_agent_override_ = user_agent;
}
blink::UserAgentMetadata ElectronBrowserClient::GetUserAgentMetadata() {
return embedder_support::GetUserAgentMetadata();
}
void ElectronBrowserClient::RegisterNonNetworkNavigationURLLoaderFactories(
int frame_tree_node_id,
ukm::SourceIdObj ukm_source_id,

View File

@@ -93,6 +93,7 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
std::string GetUserAgent() override;
void SetUserAgent(const std::string& user_agent);
blink::UserAgentMetadata GetUserAgentMetadata() override;
content::SerialDelegate* GetSerialDelegate() override;

View File

@@ -107,8 +107,6 @@ ElectronBrowserContext::ElectronBrowserContext(const std::string& partition,
protocol_registry_(base::WrapUnique(new ProtocolRegistry)),
in_memory_(in_memory),
ssl_config_(network::mojom::SSLConfig::New()) {
user_agent_ = ElectronBrowserClient::Get()->GetUserAgent();
// Read options.
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
use_cache_ = !command_line->HasSwitch(switches::kDisableHttpCache);
@@ -305,6 +303,11 @@ ElectronBrowserContext::GetSpecialStoragePolicy() {
}
std::string ElectronBrowserContext::GetUserAgent() const {
return user_agent_.value_or(ElectronBrowserClient::Get()->GetUserAgent());
}
absl::optional<std::string> ElectronBrowserContext::GetUserAgentOverride()
const {
return user_agent_;
}

View File

@@ -85,6 +85,7 @@ class ElectronBrowserContext : public content::BrowserContext {
void SetUserAgent(const std::string& user_agent);
std::string GetUserAgent() const;
absl::optional<std::string> GetUserAgentOverride() const;
bool CanUseHttpCache() const;
int GetMaxCacheSize() const;
ResolveProxyHelper* GetResolveProxyHelper();
@@ -170,7 +171,7 @@ class ElectronBrowserContext : public content::BrowserContext {
std::unique_ptr<predictors::PreconnectManager> preconnect_manager_;
std::unique_ptr<ProtocolRegistry> protocol_registry_;
std::string user_agent_;
absl::optional<std::string> user_agent_;
base::FilePath path_;
bool in_memory_ = false;
bool use_cache_ = true;

View File

@@ -371,6 +371,10 @@ void ElectronBrowserMainParts::ToolkitInitialized() {
electron::UninitializeElectron_gtk();
}
electron::InitializeElectron_gdk_pixbuf(gtk::GetLibGdkPixbuf());
CHECK(electron::IsElectron_gdk_pixbufInitialized())
<< "Failed to initialize libgdk_pixbuf-2.0.so.0";
// Chromium does not respect GTK dark theme setting, but they may change
// in future and this code might be no longer needed. Check the Chromium
// issue to keep updated:

View File

@@ -27,6 +27,9 @@ void NativeBrowserViewViews::SetAutoResizeFlags(uint8_t flags) {
void NativeBrowserViewViews::UpdateDraggableRegions(
const std::vector<mojom::DraggableRegionPtr>& regions) {
if (&draggable_regions_ != &regions)
draggable_regions_ = mojo::Clone(regions);
// We need to snap the regions to the bounds of the current BrowserView.
// For example, if an attached BrowserView is draggable but its bounds are
// { x: 200, y: 100, width: 300, height: 300 }
@@ -35,12 +38,10 @@ void NativeBrowserViewViews::UpdateDraggableRegions(
// assumed that the regions begin in the top left corner as they
// would for the main client window.
auto const offset = GetBounds().OffsetFromOrigin();
auto snapped_regions = mojo::Clone(regions);
for (auto& snapped_region : snapped_regions) {
for (auto& snapped_region : draggable_regions_) {
snapped_region->bounds.Offset(offset);
}
draggable_region_ = DraggableRegionsToSkRegion(snapped_regions);
draggable_region_ = DraggableRegionsToSkRegion(draggable_regions_);
}
void NativeBrowserViewViews::SetAutoResizeProportions(
@@ -128,6 +129,12 @@ void NativeBrowserViewViews::SetBounds(const gfx::Rect& bounds) {
auto* view = iwc_view->GetView();
view->SetBoundsRect(bounds);
ResetAutoResizeProportions();
view->InvalidateLayout();
view->SchedulePaint();
// Ensure draggable regions are properly updated to reflect new bounds.
UpdateDraggableRegions(draggable_regions_);
}
gfx::Rect NativeBrowserViewViews::GetBounds() {

View File

@@ -165,6 +165,8 @@ class NativeWindowMac : public NativeWindow,
void UpdateVibrancyRadii(bool fullscreen);
void UpdateWindowOriginalFrame();
// Set the attribute of NSWindow while work around a bug of zoom button.
bool HasStyleMask(NSUInteger flag) const;
void SetStyleMask(bool on, NSUInteger flag);

View File

@@ -317,7 +317,8 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
params.bounds = bounds;
params.delegate = this;
params.type = views::Widget::InitParams::TYPE_WINDOW;
params.native_widget = new ElectronNativeWidgetMac(this, styleMask, widget());
params.native_widget =
new ElectronNativeWidgetMac(this, windowType, styleMask, widget());
widget()->Init(std::move(params));
SetCanResize(resizable);
window_ = static_cast<ElectronNSWindow*>(
@@ -355,6 +356,10 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
NSWindowCollectionBehaviorIgnoresCycle)];
}
if (windowType == "panel") {
[window_ setLevel:NSFloatingWindowLevel];
}
bool focusable;
if (options.Get(options::kFocusable, &focusable) && !focusable)
[window_ setDisableKeyOrMainWindow:YES];
@@ -451,7 +456,7 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
SetContentView(new views::View());
AddContentViewLayers();
original_frame_ = [window_ frame];
UpdateWindowOriginalFrame();
original_level_ = [window_ level];
}
@@ -613,7 +618,7 @@ void NativeWindowMac::Maximize() {
// Take note of the current window size
if (IsNormal())
original_frame_ = [window_ frame];
UpdateWindowOriginalFrame();
[window_ zoom:nil];
if (!is_visible) {
@@ -657,7 +662,7 @@ void NativeWindowMac::Minimize() {
// Take note of the current window size
if (IsNormal())
original_frame_ = [window_ frame];
UpdateWindowOriginalFrame();
[window_ miniaturize:nil];
}
@@ -701,7 +706,7 @@ void NativeWindowMac::SetFullScreen(bool fullscreen) {
// Take note of the current window size
if (IsNormal())
original_frame_ = [window_ frame];
UpdateWindowOriginalFrame();
// This needs to be set here because it can be the case that
// SetFullScreen is called by a user before windowWillEnterFullScreen
@@ -737,6 +742,7 @@ void NativeWindowMac::SetBounds(const gfx::Rect& bounds, bool animate) {
[window_ setFrame:cocoa_bounds display:YES animate:animate];
user_set_bounds_maximized_ = IsMaximized() ? true : false;
UpdateWindowOriginalFrame();
}
gfx::Rect NativeWindowMac::GetBounds() {
@@ -810,6 +816,7 @@ void NativeWindowMac::MoveTop() {
}
void NativeWindowMac::SetResizable(bool resizable) {
ScopedDisableResize disable_resize;
SetStyleMask(resizable, NSWindowStyleMaskResizable);
SetCanResize(resizable);
}
@@ -1009,7 +1016,7 @@ void NativeWindowMac::SetSimpleFullScreen(bool simple_fullscreen) {
// Take note of the current window size and level
if (IsNormal()) {
original_frame_ = [window_ frame];
UpdateWindowOriginalFrame();
original_level_ = [window_ level];
}
@@ -1406,6 +1413,10 @@ void NativeWindowMac::UpdateVibrancyRadii(bool fullscreen) {
}
}
void NativeWindowMac::UpdateWindowOriginalFrame() {
original_frame_ = [window_ frame];
}
void NativeWindowMac::SetVibrancy(const std::string& type) {
NSVisualEffectView* vibrantView = [window_ vibrantView];
@@ -1510,12 +1521,15 @@ void NativeWindowMac::SetVibrancy(const std::string& type) {
void NativeWindowMac::SetWindowButtonVisibility(bool visible) {
window_button_visibility_ = visible;
// The visibility of window buttons are managed by |buttons_proxy_| if the
// style is customButtonsOnHover.
if (title_bar_style_ == TitleBarStyle::kCustomButtonsOnHover)
if (buttons_proxy_) {
if (visible)
[buttons_proxy_ redraw];
[buttons_proxy_ setVisible:visible];
else
}
if (title_bar_style_ != TitleBarStyle::kCustomButtonsOnHover)
InternalSetWindowButtonVisibility(visible);
NotifyLayoutWindowControlsOverlay();
}

View File

@@ -876,6 +876,11 @@ bool NativeWindowViews::IsMovable() {
void NativeWindowViews::SetMinimizable(bool minimizable) {
#if BUILDFLAG(IS_WIN)
FlipWindowStyle(GetAcceleratedWidget(), minimizable, WS_MINIMIZEBOX);
if (titlebar_overlay_enabled()) {
auto* frame_view =
static_cast<WinFrameView*>(widget()->non_client_view()->frame_view());
frame_view->caption_button_container()->UpdateButtons();
}
#endif
minimizable_ = minimizable;
}
@@ -891,6 +896,11 @@ bool NativeWindowViews::IsMinimizable() {
void NativeWindowViews::SetMaximizable(bool maximizable) {
#if BUILDFLAG(IS_WIN)
FlipWindowStyle(GetAcceleratedWidget(), maximizable, WS_MAXIMIZEBOX);
if (titlebar_overlay_enabled()) {
auto* frame_view =
static_cast<WinFrameView*>(widget()->non_client_view()->frame_view());
frame_view->caption_button_container()->UpdateButtons();
}
#endif
maximizable_ = maximizable;
}
@@ -926,6 +936,11 @@ void NativeWindowViews::SetClosable(bool closable) {
} else {
EnableMenuItem(menu, SC_CLOSE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
}
if (titlebar_overlay_enabled()) {
auto* frame_view =
static_cast<WinFrameView*>(widget()->non_client_view()->frame_view());
frame_view->caption_button_container()->UpdateButtons();
}
#endif
}

View File

@@ -50,8 +50,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 20,0,0,4
PRODUCTVERSION 20,0,0,4
FILEVERSION 20,0,0,6
PRODUCTVERSION 20,0,0,6
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L

View File

@@ -5,6 +5,8 @@
#ifndef ELECTRON_SHELL_BROWSER_UI_COCOA_ELECTRON_NATIVE_WIDGET_MAC_H_
#define ELECTRON_SHELL_BROWSER_UI_COCOA_ELECTRON_NATIVE_WIDGET_MAC_H_
#include <string>
#include "ui/views/widget/native_widget_mac.h"
namespace electron {
@@ -14,6 +16,7 @@ class NativeWindowMac;
class ElectronNativeWidgetMac : public views::NativeWidgetMac {
public:
ElectronNativeWidgetMac(NativeWindowMac* shell,
const std::string& window_type,
NSUInteger style_mask,
views::internal::NativeWidgetDelegate* delegate);
~ElectronNativeWidgetMac() override;
@@ -29,6 +32,7 @@ class ElectronNativeWidgetMac : public views::NativeWidgetMac {
private:
NativeWindowMac* shell_;
std::string window_type_;
NSUInteger style_mask_;
};

View File

@@ -4,24 +4,34 @@
#include "shell/browser/ui/cocoa/electron_native_widget_mac.h"
#include <string>
#include "shell/browser/ui/cocoa/electron_ns_panel.h"
#include "shell/browser/ui/cocoa/electron_ns_window.h"
namespace electron {
ElectronNativeWidgetMac::ElectronNativeWidgetMac(
NativeWindowMac* shell,
const std::string& window_type,
NSUInteger style_mask,
views::internal::NativeWidgetDelegate* delegate)
: views::NativeWidgetMac(delegate),
shell_(shell),
window_type_(window_type),
style_mask_(style_mask) {}
ElectronNativeWidgetMac::~ElectronNativeWidgetMac() = default;
NativeWidgetMacNSWindow* ElectronNativeWidgetMac::CreateNSWindow(
const remote_cocoa::mojom::CreateWindowParams* params) {
return [[[ElectronNSWindow alloc] initWithShell:shell_
styleMask:style_mask_] autorelease];
if (window_type_ == "panel") {
return [[[ElectronNSPanel alloc] initWithShell:shell_
styleMask:style_mask_] autorelease];
} else {
return [[[ElectronNSWindow alloc] initWithShell:shell_
styleMask:style_mask_] autorelease];
}
}
} // namespace electron

View File

@@ -0,0 +1,17 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_UI_COCOA_ELECTRON_NS_PANEL_H_
#define ELECTRON_SHELL_BROWSER_UI_COCOA_ELECTRON_NS_PANEL_H_
#include "shell/browser/ui/cocoa/electron_ns_window.h"
@interface ElectronNSPanel : ElectronNSWindow
@property NSWindowStyleMask styleMask;
@property NSWindowStyleMask originalStyleMask;
- (id)initWithShell:(electron::NativeWindowMac*)shell
styleMask:(NSUInteger)styleMask;
@end
#endif // ELECTRON_SHELL_BROWSER_UI_COCOA_ELECTRON_NS_PANEL_H_

View File

@@ -0,0 +1,39 @@
// Copyright (c) 2022 Microsoft, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/ui/cocoa/electron_ns_panel.h"
@implementation ElectronNSPanel
@synthesize originalStyleMask;
- (id)initWithShell:(electron::NativeWindowMac*)shell
styleMask:(NSUInteger)styleMask {
if (self = [super initWithShell:shell styleMask:styleMask]) {
originalStyleMask = styleMask;
}
return self;
}
@dynamic styleMask;
// The Nonactivating mask is reserverd for NSPanel,
// but we can use this workaround to add it at runtime
- (NSWindowStyleMask)styleMask {
return originalStyleMask | NSWindowStyleMaskNonactivatingPanel;
}
- (void)setStyleMask:(NSWindowStyleMask)styleMask {
originalStyleMask = styleMask;
// Notify change of style mask.
[super setStyleMask:styleMask];
}
- (void)setCollectionBehavior:(NSWindowCollectionBehavior)collectionBehavior {
NSWindowCollectionBehavior panelBehavior =
(NSWindowCollectionBehaviorCanJoinAllSpaces |
NSWindowCollectionBehaviorFullScreenAuxiliary);
[super setCollectionBehavior:collectionBehavior | panelBehavior];
}
@end

View File

@@ -16,13 +16,14 @@ class NativeWindowMac;
// Prevents window from resizing during the scope.
class ScopedDisableResize {
public:
ScopedDisableResize() { disable_resize_ = true; }
~ScopedDisableResize() { disable_resize_ = false; }
ScopedDisableResize() { disable_resize_++; }
~ScopedDisableResize() { disable_resize_--; }
static bool IsResizeDisabled() { return disable_resize_; }
// True if there are 1+ nested ScopedDisableResize objects in the scope
static bool IsResizeDisabled() { return disable_resize_ > 0; }
private:
static bool disable_resize_;
static int disable_resize_;
};
} // namespace electron

View File

@@ -13,7 +13,7 @@
namespace electron {
bool ScopedDisableResize::disable_resize_ = false;
int ScopedDisableResize::disable_resize_ = 0;
} // namespace electron

View File

@@ -203,6 +203,7 @@ using FullScreenTransitionState =
// windowDidDeminiaturize
level_ = [window level];
shell_->SetWindowLevel(NSNormalWindowLevel);
shell_->UpdateWindowOriginalFrame();
}
- (void)windowDidMiniaturize:(NSNotification*)notification {

View File

@@ -10,53 +10,78 @@
#include "base/strings/sys_string_conversions.h"
#include "shell/browser/ui/drag_util.h"
namespace electron {
// Contents largely copied from
// chrome/browser/download/drag_download_item_mac.mm.
@interface DragDownloadItemSource : NSObject <NSDraggingSource>
@end
@implementation DragDownloadItemSource
- (NSDragOperation)draggingSession:(NSDraggingSession*)session
sourceOperationMaskForDraggingContext:(NSDraggingContext)context {
return NSDragOperationEvery;
}
@end
namespace {
// Write information about the file being dragged to the pasteboard.
void AddFilesToPasteboard(NSPasteboard* pasteboard,
const std::vector<base::FilePath>& files) {
NSMutableArray* fileList = [NSMutableArray array];
for (const base::FilePath& file : files)
[fileList addObject:base::SysUTF8ToNSString(file.value())];
[pasteboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType]
owner:nil];
[pasteboard setPropertyList:fileList forType:NSFilenamesPboardType];
id<NSDraggingSource> GetDraggingSource() {
static id<NSDraggingSource> source = [[DragDownloadItemSource alloc] init];
return source;
}
} // namespace
namespace electron {
void DragFileItems(const std::vector<base::FilePath>& files,
const gfx::Image& icon,
gfx::NativeView view) {
NSPasteboard* pasteboard =
[NSPasteboard pasteboardWithName:NSPasteboardNameDrag];
AddFilesToPasteboard(pasteboard, files);
auto* native_view = view.GetNativeNSView();
NSPoint current_position =
[[native_view window] mouseLocationOutsideOfEventStream];
current_position =
[native_view backingAlignedRect:NSMakeRect(current_position.x,
current_position.y, 0, 0)
options:NSAlignAllEdgesOutward]
.origin;
NSMutableArray* file_items = [NSMutableArray array];
for (auto const& file : files) {
NSURL* file_url =
[NSURL fileURLWithPath:base::SysUTF8ToNSString(file.value())];
NSDraggingItem* file_item = [[[NSDraggingItem alloc]
initWithPasteboardWriter:file_url] autorelease];
NSImage* file_image = icon.ToNSImage();
NSSize image_size = file_image.size;
NSRect image_rect = NSMakeRect(current_position.x - image_size.width / 2,
current_position.y - image_size.height / 2,
image_size.width, image_size.height);
[file_item setDraggingFrame:image_rect contents:file_image];
[file_items addObject:file_item];
}
// Synthesize a drag event, since we don't have access to the actual event
// that initiated a drag (possibly consumed by the Web UI, for example).
NSWindow* window = [view.GetNativeNSView() window];
NSPoint position = [window mouseLocationOutsideOfEventStream];
NSPoint position = [[native_view window] mouseLocationOutsideOfEventStream];
NSTimeInterval eventTime = [[NSApp currentEvent] timestamp];
NSEvent* dragEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDragged
location:position
modifierFlags:NSEventMaskLeftMouseDragged
timestamp:eventTime
windowNumber:[window windowNumber]
context:nil
eventNumber:0
clickCount:1
pressure:1.0];
NSEvent* dragEvent =
[NSEvent mouseEventWithType:NSEventTypeLeftMouseDragged
location:position
modifierFlags:NSEventMaskLeftMouseDragged
timestamp:eventTime
windowNumber:[[native_view window] windowNumber]
context:nil
eventNumber:0
clickCount:1
pressure:1.0];
// Run the drag operation.
[window dragImage:icon.ToNSImage()
at:position
offset:NSZeroSize
event:dragEvent
pasteboard:pasteboard
source:view.GetNativeNSView()
slideBack:YES];
[native_view beginDraggingSessionWithItems:file_items
event:dragEvent
source:GetDraggingSource()];
}
} // namespace electron

View File

@@ -0,0 +1,3 @@
GdkPixbuf* gdk_pixbuf_new(GdkColorspace colorspace, gboolean has_alpha, int bits_per_sample, int width, int height)
GdkPixbuf* gdk_pixbuf_scale_simple(const GdkPixbuf* src, int dest_width, int dest_height, GdkInterpType interp_type)
guchar* gdk_pixbuf_get_pixels(const GdkPixbuf* pixbuf)

View File

@@ -1 +1,2 @@
#include <gtk/gtk.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gtk/gtk.h>

View File

@@ -4,4 +4,4 @@ void gtk_native_dialog_show(GtkNativeDialog* self);
void gtk_native_dialog_hide(GtkNativeDialog* self);
gint gtk_native_dialog_run(GtkNativeDialog* self);
void gtk_native_dialog_destroy(GtkNativeDialog* self);
GType gtk_native_dialog_get_type(void);
GType gtk_native_dialog_get_type(void);

View File

@@ -32,8 +32,11 @@ static const int kPreviewWidth = 256;
static const int kPreviewHeight = 512;
std::string MakeCaseInsensitivePattern(const std::string& extension) {
std::string pattern("*.");
// If the extension is the "all files" extension, no change needed.
if (extension == "*")
return extension;
std::string pattern("*.");
for (std::size_t i = 0, n = extension.size(); i < n; i++) {
char ch = extension[i];
if (!base::IsAsciiAlpha(ch)) {

View File

@@ -12,6 +12,7 @@
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "electron/electron_gtk_stubs.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkUnPreMultiply.h"

View File

@@ -11,6 +11,7 @@
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "electron/electron_gtk_stubs.h"
#include "shell/browser/browser.h"
#include "shell/browser/native_window_observer.h"
#include "shell/browser/native_window_views.h"

View File

@@ -174,7 +174,7 @@ void AutofillPopupView::DrawAutofillEntry(gfx::Canvas* canvas,
const int text_align =
is_rtl ? gfx::Canvas::TEXT_ALIGN_RIGHT : gfx::Canvas::TEXT_ALIGN_LEFT;
gfx::Rect value_rect = entry_rect;
value_rect.Inset(gfx::Insets::VH(kEndPadding, 0));
value_rect.Inset(gfx::Insets::VH(0, kEndPadding));
int x_align_left = value_rect.x();
const int value_width = gfx::GetStringWidth(

View File

@@ -148,11 +148,30 @@ void WinCaptionButtonContainer::UpdateButtons() {
restore_button_->SetVisible(is_maximized);
maximize_button_->SetVisible(!is_maximized);
const bool minimizable = frame_view_->window()->IsMinimizable();
minimize_button_->SetEnabled(minimizable);
// In touch mode, windows cannot be taken out of fullscreen or tiled mode, so
// the maximize/restore button should be disabled.
const bool is_touch = ui::TouchUiController::Get()->touch_ui();
restore_button_->SetEnabled(!is_touch);
maximize_button_->SetEnabled(!is_touch);
// The maximize button should only be enabled if the window is
// maximizable *and* touch mode is disabled.
const bool maximizable = frame_view_->window()->IsMaximizable();
maximize_button_->SetEnabled(!is_touch && maximizable);
const bool closable = frame_view_->window()->IsClosable();
close_button_->SetEnabled(closable);
// If all three of closable, maximizable, and minimizable are disabled,
// Windows natively only shows the disabled closable button. Copy that
// behavior here.
if (!maximizable && !closable && !minimizable) {
minimize_button_->SetVisible(false);
maximize_button_->SetVisible(false);
}
InvalidateLayout();
}
} // namespace electron

View File

@@ -41,6 +41,11 @@ class WinCaptionButtonContainer : public views::View,
gfx::Size GetButtonSize() const;
void SetButtonSize(gfx::Size size);
// Sets caption button visibility and enabled state based on window state.
// Only one of maximize or restore button should ever be visible at the same
// time, and both are disabled in tablet UI mode.
void UpdateButtons();
private:
// views::View:
void AddedToWidget() override;
@@ -52,11 +57,6 @@ class WinCaptionButtonContainer : public views::View,
void ResetWindowControls();
// Sets caption button visibility and enabled state based on window state.
// Only one of maximize or restore button should ever be visible at the same
// time, and both are disabled in tablet UI mode.
void UpdateButtons();
WinFrameView* const frame_view_;
WinCaptionButton* const minimize_button_;
WinCaptionButton* const maximize_button_;

View File

@@ -13,6 +13,7 @@
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/views/frameless_view.h"
#include "shell/browser/ui/views/win_caption_button.h"
#include "shell/browser/ui/views/win_caption_button_container.h"
namespace electron {
@@ -43,6 +44,9 @@ class WinFrameView : public FramelessView {
NativeWindowViews* window() const { return window_; }
views::Widget* frame() const { return frame_; }
WinCaptionButtonContainer* caption_button_container() {
return caption_button_container_;
}
bool IsMaximized() const;

View File

@@ -7,13 +7,10 @@
#include "base/win/windows_version.h"
#include "electron/buildflags/buildflags.h"
#include "shell/browser/ui/views/win_frame_view.h"
#include "shell/browser/win/dark_mode.h"
#include "ui/base/win/hwnd_metrics.h"
#include "ui/base/win/shell.h"
#if BUILDFLAG(ENABLE_WIN_DARK_MODE_WINDOW_UI)
#include "shell/browser/win/dark_mode.h"
#endif
namespace electron {
ElectronDesktopWindowTreeHostWin::ElectronDesktopWindowTreeHostWin(
@@ -29,14 +26,13 @@ bool ElectronDesktopWindowTreeHostWin::PreHandleMSG(UINT message,
WPARAM w_param,
LPARAM l_param,
LRESULT* result) {
#if BUILDFLAG(ENABLE_WIN_DARK_MODE_WINDOW_UI)
if (message == WM_NCCREATE) {
HWND const hwnd = GetAcceleratedWidget();
auto const theme_source =
ui::NativeTheme::GetInstanceForNativeUi()->theme_source();
win::SetDarkModeForWindow(hwnd, theme_source);
const bool dark_mode_supported = win::IsDarkModeSupported();
if (dark_mode_supported && message == WM_NCCREATE) {
win::SetDarkModeForWindow(GetAcceleratedWidget());
ui::NativeTheme::GetInstanceForNativeUi()->AddObserver(this);
} else if (dark_mode_supported && message == WM_DESTROY) {
ui::NativeTheme::GetInstanceForNativeUi()->RemoveObserver(this);
}
#endif
return native_window_view_->PreHandleMSG(message, w_param, l_param, result);
}
@@ -99,4 +95,9 @@ bool ElectronDesktopWindowTreeHostWin::GetClientAreaInsets(
return false;
}
void ElectronDesktopWindowTreeHostWin::OnNativeThemeUpdated(
ui::NativeTheme* observed_theme) {
win::SetDarkModeForWindow(GetAcceleratedWidget());
}
} // namespace electron

View File

@@ -12,8 +12,8 @@
namespace electron {
class ElectronDesktopWindowTreeHostWin
: public views::DesktopWindowTreeHostWin {
class ElectronDesktopWindowTreeHostWin : public views::DesktopWindowTreeHostWin,
public ::ui::NativeThemeObserver {
public:
ElectronDesktopWindowTreeHostWin(
NativeWindowViews* native_window_view,
@@ -37,6 +37,9 @@ class ElectronDesktopWindowTreeHostWin
bool GetClientAreaInsets(gfx::Insets* insets,
HMONITOR monitor) const override;
// ui::NativeThemeObserver:
void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override;
private:
NativeWindowViews* native_window_view_; // weak ref
};

View File

@@ -486,10 +486,6 @@ void WebContentsPreferences::OverrideWebkitPrefs(
prefs->offscreen = offscreen_;
// The preload script.
if (preload_path_)
prefs->preload = *preload_path_;
prefs->node_integration = node_integration_;
prefs->node_integration_in_worker = node_integration_in_worker_;
prefs->node_integration_in_sub_frames = node_integration_in_sub_frames_;

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2020 Microsoft Inc. All rights reserved.
// Copyright (c) 2022 Microsoft Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
@@ -6,173 +6,57 @@
#include <dwmapi.h> // DwmSetWindowAttribute()
#include "base/files/file_path.h"
#include "base/scoped_native_library.h"
#include "base/win/pe_image.h"
#include "base/win/win_util.h"
#include "base/win/windows_version.h"
// This flag works since Win10 20H1 but is not documented until Windows 11
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
// This namespace contains code originally from
// https://github.com/ysc3839/win32-darkmode/
// governed by the MIT license and (c) Richard Yu
// https://github.com/microsoft/terminal
// governed by the MIT license and (c) Microsoft Corporation.
namespace {
// 1903 18362
enum PreferredAppMode { Default, AllowDark, ForceDark, ForceLight, Max };
// https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
HRESULT TrySetWindowTheme(HWND hWnd, bool dark) {
const BOOL isDarkMode = dark;
HRESULT result = DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE,
&isDarkMode, sizeof(isDarkMode));
bool g_darkModeSupported = false;
bool g_darkModeEnabled = false;
DWORD g_buildNumber = 0;
if (FAILED(result))
return result;
enum WINDOWCOMPOSITIONATTRIB {
WCA_USEDARKMODECOLORS = 26 // build 18875+
};
struct WINDOWCOMPOSITIONATTRIBDATA {
WINDOWCOMPOSITIONATTRIB Attrib;
PVOID pvData;
SIZE_T cbData;
};
using fnSetWindowCompositionAttribute =
BOOL(WINAPI*)(HWND hWnd, WINDOWCOMPOSITIONATTRIBDATA*);
fnSetWindowCompositionAttribute _SetWindowCompositionAttribute = nullptr;
bool IsHighContrast() {
HIGHCONTRASTW highContrast = {sizeof(highContrast)};
if (SystemParametersInfoW(SPI_GETHIGHCONTRAST, sizeof(highContrast),
&highContrast, FALSE))
return highContrast.dwFlags & HCF_HIGHCONTRASTON;
return false;
}
void RefreshTitleBarThemeColor(HWND hWnd, bool dark) {
LONG ldark = dark;
if (g_buildNumber >= 20161) {
// DWMA_USE_IMMERSIVE_DARK_MODE = 20
DwmSetWindowAttribute(hWnd, 20, &ldark, sizeof dark);
return;
}
if (g_buildNumber >= 18363) {
auto data = WINDOWCOMPOSITIONATTRIBDATA{WCA_USEDARKMODECOLORS, &ldark,
sizeof ldark};
_SetWindowCompositionAttribute(hWnd, &data);
return;
}
DwmSetWindowAttribute(hWnd, 0x13, &ldark, sizeof ldark);
}
void InitDarkMode() {
// confirm that we're running on a version of Windows
// where the Dark Mode API is known
auto* os_info = base::win::OSInfo::GetInstance();
g_buildNumber = os_info->version_number().build;
auto const version = os_info->version();
if ((version < base::win::Version::WIN10_RS5) ||
(version > base::win::Version::WIN10_20H1)) {
return;
// Toggle the nonclient area active state to force a redraw (Win10 workaround)
if (version < base::win::Version::WIN11) {
HWND activeWindow = GetActiveWindow();
SendMessage(hWnd, WM_NCACTIVATE, hWnd != activeWindow, 0);
SendMessage(hWnd, WM_NCACTIVATE, hWnd == activeWindow, 0);
}
// load "SetWindowCompositionAttribute", used in RefreshTitleBarThemeColor()
_SetWindowCompositionAttribute =
reinterpret_cast<decltype(_SetWindowCompositionAttribute)>(
base::win::GetUser32FunctionPointer("SetWindowCompositionAttribute"));
if (_SetWindowCompositionAttribute == nullptr) {
return;
}
// load the dark mode functions from uxtheme.dll
// * RefreshImmersiveColorPolicyState()
// * ShouldAppsUseDarkMode()
// * AllowDarkModeForApp()
// * SetPreferredAppMode()
// * AllowDarkModeForApp() (build < 18362)
// * SetPreferredAppMode() (build >= 18362)
base::NativeLibrary uxtheme =
base::PinSystemLibrary(FILE_PATH_LITERAL("uxtheme.dll"));
if (!uxtheme) {
return;
}
auto ux_pei = base::win::PEImage(uxtheme);
auto get_ux_proc_from_ordinal = [&ux_pei](int ordinal, auto* setme) {
FARPROC proc = ux_pei.GetProcAddress(reinterpret_cast<LPCSTR>(ordinal));
*setme = reinterpret_cast<decltype(*setme)>(proc);
};
// ordinal 104
using fnRefreshImmersiveColorPolicyState = VOID(WINAPI*)();
fnRefreshImmersiveColorPolicyState _RefreshImmersiveColorPolicyState = {};
get_ux_proc_from_ordinal(104, &_RefreshImmersiveColorPolicyState);
// ordinal 132
using fnShouldAppsUseDarkMode = BOOL(WINAPI*)();
fnShouldAppsUseDarkMode _ShouldAppsUseDarkMode = {};
get_ux_proc_from_ordinal(132, &_ShouldAppsUseDarkMode);
// ordinal 135, in 1809
using fnAllowDarkModeForApp = BOOL(WINAPI*)(BOOL allow);
fnAllowDarkModeForApp _AllowDarkModeForApp = {};
// ordinal 135, in 1903
typedef PreferredAppMode(WINAPI *
fnSetPreferredAppMode)(PreferredAppMode appMode);
fnSetPreferredAppMode _SetPreferredAppMode = {};
if (g_buildNumber < 18362) {
get_ux_proc_from_ordinal(135, &_AllowDarkModeForApp);
} else {
get_ux_proc_from_ordinal(135, &_SetPreferredAppMode);
}
// dark mode is supported iff we found the functions
g_darkModeSupported = _RefreshImmersiveColorPolicyState &&
_ShouldAppsUseDarkMode &&
(_AllowDarkModeForApp || _SetPreferredAppMode);
if (!g_darkModeSupported) {
return;
}
// initial setup: allow dark mode to be used
if (_AllowDarkModeForApp) {
_AllowDarkModeForApp(true);
} else if (_SetPreferredAppMode) {
_SetPreferredAppMode(AllowDark);
}
_RefreshImmersiveColorPolicyState();
// check to see if dark mode is currently enabled
g_darkModeEnabled = _ShouldAppsUseDarkMode() && !IsHighContrast();
return S_OK;
}
} // namespace
namespace electron {
void EnsureInitialized() {
static bool initialized = false;
if (!initialized) {
initialized = true;
::InitDarkMode();
}
}
bool IsDarkPreferred(ui::NativeTheme::ThemeSource theme_source) {
switch (theme_source) {
case ui::NativeTheme::ThemeSource::kForcedLight:
return false;
case ui::NativeTheme::ThemeSource::kForcedDark:
return g_darkModeSupported;
case ui::NativeTheme::ThemeSource::kSystem:
return g_darkModeEnabled;
}
}
namespace win {
void SetDarkModeForWindow(HWND hWnd,
ui::NativeTheme::ThemeSource theme_source) {
EnsureInitialized();
RefreshTitleBarThemeColor(hWnd, IsDarkPreferred(theme_source));
bool IsDarkModeSupported() {
auto* os_info = base::win::OSInfo::GetInstance();
auto const version = os_info->version();
return version >= base::win::Version::WIN10_20H1;
}
void SetDarkModeForWindow(HWND hWnd) {
ui::NativeTheme* theme = ui::NativeTheme::GetInstanceForNativeUi();
bool dark =
theme->ShouldUseDarkColors() && !theme->UserHasContrastPreference();
TrySetWindowTheme(hWnd, dark);
}
} // namespace win

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2020 Microsoft Inc. All rights reserved.
// Copyright (c) 2022 Microsoft Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
@@ -19,7 +19,8 @@ namespace electron {
namespace win {
void SetDarkModeForWindow(HWND hWnd, ui::NativeTheme::ThemeSource theme_source);
bool IsDarkModeSupported();
void SetDarkModeForWindow(HWND hWnd);
} // namespace win

View File

@@ -54,10 +54,6 @@ bool IsPictureInPictureEnabled() {
return BUILDFLAG(ENABLE_PICTURE_IN_PICTURE);
}
bool IsWinDarkModeWindowUiEnabled() {
return BUILDFLAG(ENABLE_WIN_DARK_MODE_WINDOW_UI);
}
bool IsComponentBuild() {
#if defined(COMPONENT_BUILD)
return true;
@@ -84,7 +80,6 @@ void Initialize(v8::Local<v8::Object> exports,
dict.SetMethod("isPictureInPictureEnabled", &IsPictureInPictureEnabled);
dict.SetMethod("isComponentBuild", &IsComponentBuild);
dict.SetMethod("isExtensionsEnabled", &IsExtensionsEnabled);
dict.SetMethod("isWinDarkModeWindowUiEnabled", &IsWinDarkModeWindowUiEnabled);
}
} // namespace

View File

@@ -11,6 +11,7 @@
#include <iomanip>
#include <string>
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/mac/bundle_locations.h"
#include "base/mac/foundation_util.h"
@@ -21,7 +22,8 @@
namespace asar {
absl::optional<base::FilePath> Archive::RelativePath() const {
base::FilePath bundle_path = base::mac::MainBundlePath().Append("Contents");
base::FilePath bundle_path = base::MakeAbsoluteFilePath(
base::mac::MainBundlePath().Append("Contents"));
base::FilePath relative_path;
if (!bundle_path.AppendRelativePath(path_, &relative_path))

View File

@@ -55,13 +55,16 @@
V(electron_browser_in_app_purchase) \
V(electron_browser_menu) \
V(electron_browser_message_port) \
V(electron_browser_native_theme) \
V(electron_browser_net) \
V(electron_browser_notification) \
V(electron_browser_power_monitor) \
V(electron_browser_power_save_blocker) \
V(electron_browser_protocol) \
V(electron_browser_printing) \
V(electron_browser_safe_storage) \
V(electron_browser_session) \
V(electron_browser_screen) \
V(electron_browser_system_preferences) \
V(electron_browser_base_window) \
V(electron_browser_tray) \
@@ -77,9 +80,6 @@
V(electron_common_environment) \
V(electron_common_features) \
V(electron_common_native_image) \
V(electron_common_native_theme) \
V(electron_common_notification) \
V(electron_common_screen) \
V(electron_common_shell) \
V(electron_common_v8_util) \
V(electron_renderer_context_bridge) \

View File

@@ -496,9 +496,7 @@ class WebFrameRenderer : public gin::Wrappable<WebFrameRenderer>,
const auto& prefs = render_frame->GetBlinkPreferences();
if (pref_name == options::kPreloadScripts) {
return gin::ConvertToV8(isolate, prefs.preloads);
} else if (pref_name == "isWebView") {
if (pref_name == "isWebView") {
// FIXME(zcbenz): For child windows opened with window.open('') from
// webview, the WebPreferences is inherited from webview and the value
// of |is_webview| is wrong.
@@ -513,8 +511,6 @@ class WebFrameRenderer : public gin::Wrappable<WebFrameRenderer>,
return gin::ConvertToV8(isolate, prefs.hidden_page);
} else if (pref_name == options::kOffscreen) {
return gin::ConvertToV8(isolate, prefs.offscreen);
} else if (pref_name == options::kPreloadScript) {
return gin::ConvertToV8(isolate, prefs.preload.value());
} else if (pref_name == options::kNodeIntegration) {
return gin::ConvertToV8(isolate, prefs.node_integration);
} else if (pref_name == options::kNodeIntegrationInWorker) {

View File

@@ -24,15 +24,6 @@
namespace electron {
namespace {
bool IsDevToolsExtension(content::RenderFrame* render_frame) {
return static_cast<GURL>(render_frame->GetWebFrame()->GetDocument().Url())
.SchemeIs("chrome-extension");
}
} // namespace
ElectronRendererClient::ElectronRendererClient()
: node_bindings_(
NodeBindings::Create(NodeBindings::BrowserEnvironment::kRenderer)),
@@ -77,15 +68,7 @@ void ElectronRendererClient::DidCreateScriptContext(
// Only load Node.js if we are a main frame or a devtools extension
// unless Node.js support has been explicitly enabled for subframes.
auto prefs = render_frame->GetBlinkPreferences();
bool is_main_frame = render_frame->IsMainFrame();
bool is_devtools = IsDevToolsExtension(render_frame);
bool allow_node_in_subframes = prefs.node_integration_in_sub_frames;
bool should_load_node =
(is_main_frame || is_devtools || allow_node_in_subframes) &&
!IsWebViewFrame(renderer_context, render_frame);
if (!should_load_node)
if (!ShouldLoadPreload(renderer_context, render_frame))
return;
injected_frames_.insert(render_frame);

View File

@@ -36,16 +36,6 @@ namespace {
const char kLifecycleKey[] = "lifecycle";
const char kModuleCacheKey[] = "native-module-cache";
bool IsDevTools(content::RenderFrame* render_frame) {
return render_frame->GetWebFrame()->GetDocument().Url().ProtocolIs(
"devtools");
}
bool IsDevToolsExtension(content::RenderFrame* render_frame) {
return render_frame->GetWebFrame()->GetDocument().Url().ProtocolIs(
"chrome-extension");
}
v8::Local<v8::Object> GetModuleCache(v8::Isolate* isolate) {
auto context = isolate->GetCurrentContext();
gin_helper::Dictionary global(isolate, context->Global());
@@ -207,17 +197,7 @@ void ElectronSandboxedRendererClient::DidCreateScriptContext(
// Only allow preload for the main frame or
// For devtools we still want to run the preload_bundle script
// Or when nodeSupport is explicitly enabled in sub frames
bool is_main_frame = render_frame->IsMainFrame();
bool is_devtools =
IsDevTools(render_frame) || IsDevToolsExtension(render_frame);
bool allow_node_in_sub_frames =
render_frame->GetBlinkPreferences().node_integration_in_sub_frames;
bool should_load_preload =
(is_main_frame || is_devtools || allow_node_in_sub_frames) &&
!IsWebViewFrame(context, render_frame);
if (!should_load_preload)
if (!ShouldLoadPreload(context, render_frame))
return;
injected_frames_.insert(render_frame);

View File

@@ -134,6 +134,16 @@ class ChromePdfInternalPluginDelegate final
// static
RendererClientBase* g_renderer_client_base = nullptr;
bool IsDevTools(content::RenderFrame* render_frame) {
return render_frame->GetWebFrame()->GetDocument().Url().ProtocolIs(
"devtools");
}
bool IsDevToolsExtension(content::RenderFrame* render_frame) {
return render_frame->GetWebFrame()->GetDocument().Url().ProtocolIs(
"chrome-extension");
}
} // namespace
RendererClientBase::RendererClientBase() {
@@ -196,6 +206,19 @@ void RendererClientBase::BindProcess(v8::Isolate* isolate,
process->SetReadOnly("contextId", context_id);
}
bool RendererClientBase::ShouldLoadPreload(
v8::Handle<v8::Context> context,
content::RenderFrame* render_frame) const {
auto prefs = render_frame->GetBlinkPreferences();
bool is_main_frame = render_frame->IsMainFrame();
bool is_devtools =
IsDevTools(render_frame) || IsDevToolsExtension(render_frame);
bool allow_node_in_sub_frames = prefs.node_integration_in_sub_frames;
return (is_main_frame || is_devtools || allow_node_in_sub_frames) &&
!IsWebViewFrame(context, render_frame);
}
void RendererClientBase::RenderThreadStarted() {
auto* command_line = base::CommandLine::ForCurrentProcess();

View File

@@ -93,6 +93,9 @@ class RendererClientBase : public content::ContentRendererClient
gin_helper::Dictionary* process,
content::RenderFrame* render_frame);
bool ShouldLoadPreload(v8::Handle<v8::Context> context,
content::RenderFrame* render_frame) const;
// content::ContentRendererClient:
void RenderThreadStarted() override;
void ExposeInterfacesToBrowser(mojo::BinderMap* binders) override;

View File

@@ -1228,6 +1228,7 @@ describe('BrowserWindow module', () => {
await resize;
expectBoundsEqual(w.getNormalBounds(), w.getBounds());
});
it('checks normal bounds after move', async () => {
const pos = [10, 10];
const move = emittedOnce(w, 'move');
@@ -1246,6 +1247,51 @@ describe('BrowserWindow module', () => {
await maximize;
expectBoundsEqual(w.getNormalBounds(), bounds);
});
it('updates normal bounds after resize and maximize', async () => {
const size = [300, 400];
const resize = emittedOnce(w, 'resize');
w.setSize(size[0], size[1]);
await resize;
const original = w.getBounds();
const maximize = emittedOnce(w, 'maximize');
w.maximize();
await maximize;
const normal = w.getNormalBounds();
const bounds = w.getBounds();
expect(normal).to.deep.equal(original);
expect(normal).to.not.deep.equal(bounds);
const close = emittedOnce(w, 'close');
w.close();
await close;
});
it('updates normal bounds after move and maximize', async () => {
const pos = [10, 10];
const move = emittedOnce(w, 'move');
w.setPosition(pos[0], pos[1]);
await move;
const original = w.getBounds();
const maximize = emittedOnce(w, 'maximize');
w.maximize();
await maximize;
const normal = w.getNormalBounds();
const bounds = w.getBounds();
expect(normal).to.deep.equal(original);
expect(normal).to.not.deep.equal(bounds);
const close = emittedOnce(w, 'close');
w.close();
await close;
});
it('checks normal bounds when unmaximized', async () => {
const bounds = w.getBounds();
w.once('maximize', () => {
@@ -1257,6 +1303,7 @@ describe('BrowserWindow module', () => {
await unmaximize;
expectBoundsEqual(w.getNormalBounds(), bounds);
});
it('does not change size for a frameless window with min size', async () => {
w.destroy();
w = new BrowserWindow({
@@ -1277,6 +1324,7 @@ describe('BrowserWindow module', () => {
await unmaximize;
expectBoundsEqual(w.getNormalBounds(), bounds);
});
it('correctly checks transparent window maximization state', async () => {
w.destroy();
w = new BrowserWindow({
@@ -1296,6 +1344,7 @@ describe('BrowserWindow module', () => {
await unmaximize;
expect(w.isMaximized()).to.equal(false);
});
it('returns the correct value for windows with an aspect ratio', async () => {
w.destroy();
w = new BrowserWindow({
@@ -1325,6 +1374,41 @@ describe('BrowserWindow module', () => {
await minimize;
expectBoundsEqual(w.getNormalBounds(), bounds);
});
it('updates normal bounds after move and minimize', async () => {
const pos = [10, 10];
const move = emittedOnce(w, 'move');
w.setPosition(pos[0], pos[1]);
await move;
const original = w.getBounds();
const minimize = emittedOnce(w, 'minimize');
w.minimize();
await minimize;
const normal = w.getNormalBounds();
expect(original).to.deep.equal(normal);
expectBoundsEqual(normal, w.getBounds());
});
it('updates normal bounds after resize and minimize', async () => {
const size = [300, 400];
const resize = emittedOnce(w, 'resize');
w.setSize(size[0], size[1]);
await resize;
const original = w.getBounds();
const minimize = emittedOnce(w, 'minimize');
w.minimize();
await minimize;
const normal = w.getNormalBounds();
expect(original).to.deep.equal(normal);
expectBoundsEqual(normal, w.getBounds());
});
it('checks normal bounds when restored', async () => {
const bounds = w.getBounds();
w.once('minimize', () => {
@@ -1336,6 +1420,7 @@ describe('BrowserWindow module', () => {
await restore;
expectBoundsEqual(w.getNormalBounds(), bounds);
});
it('does not change size for a frameless window with min size', async () => {
w.destroy();
w = new BrowserWindow({
@@ -1381,6 +1466,50 @@ describe('BrowserWindow module', () => {
expectBoundsEqual(w.getNormalBounds(), bounds);
});
it('updates normal bounds after resize and fullscreen', async () => {
const size = [300, 400];
const resize = emittedOnce(w, 'resize');
w.setSize(size[0], size[1]);
await resize;
const original = w.getBounds();
const fsc = emittedOnce(w, 'enter-full-screen');
w.fullScreen = true;
await fsc;
const normal = w.getNormalBounds();
const bounds = w.getBounds();
expect(normal).to.deep.equal(original);
expect(normal).to.not.deep.equal(bounds);
const close = emittedOnce(w, 'close');
w.close();
await close;
});
it('updates normal bounds after move and fullscreen', async () => {
const pos = [10, 10];
const move = emittedOnce(w, 'move');
w.setPosition(pos[0], pos[1]);
await move;
const original = w.getBounds();
const fsc = emittedOnce(w, 'enter-full-screen');
w.fullScreen = true;
await fsc;
const normal = w.getNormalBounds();
const bounds = w.getBounds();
expect(normal).to.deep.equal(original);
expect(normal).to.not.deep.equal(bounds);
const close = emittedOnce(w, 'close');
w.close();
await close;
});
it('checks normal bounds when unfullscreen\'ed', async () => {
const bounds = w.getBounds();
w.once('enter-full-screen', () => {
@@ -1418,6 +1547,50 @@ describe('BrowserWindow module', () => {
expectBoundsEqual(w.getNormalBounds(), bounds);
});
it('updates normal bounds after resize and fullscreen', async () => {
const size = [300, 400];
const resize = emittedOnce(w, 'resize');
w.setSize(size[0], size[1]);
await resize;
const original = w.getBounds();
const fsc = emittedOnce(w, 'enter-full-screen');
w.setFullScreen(true);
await fsc;
const normal = w.getNormalBounds();
const bounds = w.getBounds();
expect(normal).to.deep.equal(original);
expect(normal).to.not.deep.equal(bounds);
const close = emittedOnce(w, 'close');
w.close();
await close;
});
it('updates normal bounds after move and fullscreen', async () => {
const pos = [10, 10];
const move = emittedOnce(w, 'move');
w.setPosition(pos[0], pos[1]);
await move;
const original = w.getBounds();
const fsc = emittedOnce(w, 'enter-full-screen');
w.setFullScreen(true);
await fsc;
const normal = w.getNormalBounds();
const bounds = w.getBounds();
expect(normal).to.deep.equal(original);
expect(normal).to.not.deep.equal(bounds);
const close = emittedOnce(w, 'close');
w.close();
await close;
});
it('checks normal bounds when unfullscreen\'ed', async () => {
const bounds = w.getBounds();
w.show();
@@ -1823,6 +1996,42 @@ describe('BrowserWindow module', () => {
w.setWindowButtonVisibility(false);
expect(w._getWindowButtonVisibility()).to.equal(false);
});
it('correctly updates when entering/exiting fullscreen for hidden style', async () => {
const w = new BrowserWindow({ show: false, frame: false, titleBarStyle: 'hidden' });
expect(w._getWindowButtonVisibility()).to.equal(true);
w.setWindowButtonVisibility(false);
expect(w._getWindowButtonVisibility()).to.equal(false);
const enterFS = emittedOnce(w, 'enter-full-screen');
w.setFullScreen(true);
await enterFS;
const leaveFS = emittedOnce(w, 'leave-full-screen');
w.setFullScreen(false);
await leaveFS;
w.setWindowButtonVisibility(true);
expect(w._getWindowButtonVisibility()).to.equal(true);
});
it('correctly updates when entering/exiting fullscreen for hiddenInset style', async () => {
const w = new BrowserWindow({ show: false, frame: false, titleBarStyle: 'hiddenInset' });
expect(w._getWindowButtonVisibility()).to.equal(true);
w.setWindowButtonVisibility(false);
expect(w._getWindowButtonVisibility()).to.equal(false);
const enterFS = emittedOnce(w, 'enter-full-screen');
w.setFullScreen(true);
await enterFS;
const leaveFS = emittedOnce(w, 'leave-full-screen');
w.setFullScreen(false);
await leaveFS;
w.setWindowButtonVisibility(true);
expect(w._getWindowButtonVisibility()).to.equal(true);
});
});
ifdescribe(process.platform === 'darwin')('BrowserWindow.setVibrancy(type)', () => {
@@ -3090,7 +3299,15 @@ describe('BrowserWindow module', () => {
});
w.webContents.setWindowOpenHandler(() => ({
action: 'allow',
overrideBrowserWindowOptions: { show: false, webPreferences: { contextIsolation: false, webviewTag: true, nodeIntegrationInSubFrames: true } }
overrideBrowserWindowOptions: {
show: false,
webPreferences: {
contextIsolation: false,
webviewTag: true,
nodeIntegrationInSubFrames: true,
preload
}
}
}));
w.webContents.once('new-window', (event, url, frameName, disposition, options) => {
options.show = false;

View File

@@ -1508,6 +1508,68 @@ describe('chromium features', () => {
expect(focus).to.be.false();
});
});
describe('navigator.userAgentData', () => {
// These tests are done on an http server because navigator.userAgentData
// requires a secure context.
let server: http.Server;
let serverUrl: string;
before(async () => {
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));
serverUrl = `http://localhost:${(server.address() as any).port}`;
});
after(() => {
server.close();
});
describe('is not empty', () => {
it('by default', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL(serverUrl);
const platform = await w.webContents.executeJavaScript('navigator.userAgentData.platform');
expect(platform).not.to.be.empty();
});
it('when there is a session-wide UA override', async () => {
const ses = session.fromPartition(`${Math.random()}`);
ses.setUserAgent('foobar');
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
await w.loadURL(serverUrl);
const platform = await w.webContents.executeJavaScript('navigator.userAgentData.platform');
expect(platform).not.to.be.empty();
});
it('when there is a WebContents-specific UA override', async () => {
const w = new BrowserWindow({ show: false });
w.webContents.setUserAgent('foo');
await w.loadURL(serverUrl);
const platform = await w.webContents.executeJavaScript('navigator.userAgentData.platform');
expect(platform).not.to.be.empty();
});
it('when there is a WebContents-specific UA override at load time', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL(serverUrl, {
userAgent: 'foo'
});
const platform = await w.webContents.executeJavaScript('navigator.userAgentData.platform');
expect(platform).not.to.be.empty();
});
});
describe('brand list', () => {
it('contains chromium', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL(serverUrl);
const brands = await w.webContents.executeJavaScript('navigator.userAgentData.brands');
expect(brands.map((b: any) => b.brand)).to.include('Chromium');
});
});
});
});
describe('font fallback', () => {

View File

@@ -1,166 +0,0 @@
[
[
"top=5,left=10,resizable=no",
{
"sender": "[WebContents]",
"frameId": 1,
"processId": "placeholder-process-id"
},
"about:blank",
"frame-name",
"new-window",
{
"show": true,
"width": 800,
"height": 600,
"top": 5,
"left": 10,
"resizable": false,
"x": 10,
"y": 5,
"webPreferences": {
"contextIsolation": true,
"nodeIntegration": false,
"webviewTag": false,
"nodeIntegrationInSubFrames": false
},
"webContents": "[WebContents]"
},
[],
{
"url": "",
"policy": "strict-origin-when-cross-origin"
},
null
],
[
"zoomFactor=2,resizable=0,x=0,y=10",
{
"sender": "[WebContents]",
"frameId": 1,
"processId": "placeholder-process-id"
},
"about:blank",
"frame-name",
"new-window",
{
"show": true,
"width": 800,
"height": 600,
"resizable": false,
"x": 0,
"y": 10,
"webPreferences": {
"zoomFactor": "2",
"contextIsolation": true,
"nodeIntegration": false,
"webviewTag": false,
"nodeIntegrationInSubFrames": false
},
"webContents": "[WebContents]"
},
[],
{
"url": "",
"policy": "strict-origin-when-cross-origin"
},
null
],
[
"backgroundColor=gray,webPreferences=0,x=100,y=100",
{
"sender": "[WebContents]",
"frameId": 1,
"processId": "placeholder-process-id"
},
"about:blank",
"frame-name",
"new-window",
{
"show": true,
"width": 800,
"height": 600,
"backgroundColor": "gray",
"webPreferences": {
"contextIsolation": true,
"nodeIntegration": false,
"webviewTag": false,
"nodeIntegrationInSubFrames": false
},
"x": 100,
"y": 100,
"webContents": "[WebContents]"
},
[],
{
"url": "",
"policy": "strict-origin-when-cross-origin"
},
null
],
[
"x=50,y=20,title=sup",
{
"sender": "[WebContents]",
"frameId": 1,
"processId": "placeholder-process-id"
},
"about:blank",
"frame-name",
"new-window",
{
"show": true,
"width": 800,
"height": 600,
"x": 50,
"y": 20,
"title": "sup",
"webPreferences": {
"contextIsolation": true,
"nodeIntegration": false,
"webviewTag": false,
"nodeIntegrationInSubFrames": false
},
"webContents": "[WebContents]"
},
[],
{
"url": "",
"policy": "strict-origin-when-cross-origin"
},
null
],
[
"show=false,top=1,left=1",
{
"sender": "[WebContents]",
"frameId": 1,
"processId": "placeholder-process-id"
},
"about:blank",
"frame-name",
"new-window",
{
"show": false,
"width": 800,
"height": 600,
"top": 1,
"left": 1,
"x": 1,
"y": 1,
"webPreferences": {
"contextIsolation": true,
"nodeIntegration": false,
"webviewTag": false,
"nodeIntegrationInSubFrames": false
},
"webContents": "[WebContents]"
},
[],
{
"url": "",
"policy": "strict-origin-when-cross-origin"
},
null
]
]

Some files were not shown because too many files have changed in this diff Show More