mirror of
https://github.com/electron/electron.git
synced 2026-02-26 03:01:17 -05:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec0fa836d2 | ||
|
|
56a34d3b10 | ||
|
|
bde3d403a8 | ||
|
|
8779a3ac0f | ||
|
|
b465ee721e | ||
|
|
50b0750df3 | ||
|
|
1a3da54563 | ||
|
|
fa3767564a | ||
|
|
85db7e7077 | ||
|
|
9b1d2d5b88 | ||
|
|
e8621de3f5 | ||
|
|
9942b2ba80 | ||
|
|
2bf125f82a | ||
|
|
69493ac51e | ||
|
|
6504c4bbc8 | ||
|
|
5c1e1ee5ec | ||
|
|
f39ac0764d | ||
|
|
252805ddb4 | ||
|
|
df27597c05 | ||
|
|
fa683ef8b7 | ||
|
|
16b09a676f | ||
|
|
301108ece4 | ||
|
|
5d36cdf485 | ||
|
|
c94ab729dd | ||
|
|
b35f904e58 | ||
|
|
b143042c7f | ||
|
|
10c238c0ea | ||
|
|
ad2f34491c | ||
|
|
d6c51bc456 | ||
|
|
f313d00083 | ||
|
|
2ef55e1e9e | ||
|
|
62402870fd | ||
|
|
7b2a4c9984 | ||
|
|
7958f3efbb | ||
|
|
3e90aff6fd | ||
|
|
192fc3ee2c | ||
|
|
e0e7b14bdc | ||
|
|
4cecc87c55 | ||
|
|
46e7511af9 | ||
|
|
72733bae8a | ||
|
|
38fce95417 | ||
|
|
54de33464d | ||
|
|
6c4c66d2cd | ||
|
|
839dcbbbdb | ||
|
|
05ae55d131 | ||
|
|
d7736e5924 | ||
|
|
30921548ed | ||
|
|
9059a50f93 | ||
|
|
78569e9b91 | ||
|
|
07b8b2ffdd | ||
|
|
2d18829b43 | ||
|
|
f0a9e56741 | ||
|
|
314e627df1 |
@@ -57,6 +57,16 @@ parameters:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
# Executors
|
||||
executors:
|
||||
linux-arm:
|
||||
resource_class: electronjs/linux-arm
|
||||
machine: true
|
||||
|
||||
linux-arm64:
|
||||
resource_class: electronjs/linux-arm64
|
||||
machine: true
|
||||
|
||||
# The config expects the following environment variables to be set:
|
||||
# - "SLACK_WEBHOOK" Slack hook URL to send notifications.
|
||||
#
|
||||
@@ -239,7 +249,14 @@ step-maybe-cleanup-arm64-mac: &step-maybe-cleanup-arm64-mac
|
||||
killall Safari || echo "No Safari processes left running"
|
||||
rm -rf ~/Library/Application\ Support/Electron*
|
||||
rm -rf ~/Library/Application\ Support/electron*
|
||||
elif [ "$TARGET_ARCH" == "arm" ] || [ "$TARGET_ARCH" == "arm64" ]; then
|
||||
XVFB=/usr/bin/Xvfb
|
||||
/sbin/start-stop-daemon --stop --exec $XVFB || echo "Xvfb not running"
|
||||
pkill electron || echo "electron not running"
|
||||
rm -rf ~/.config/Electron*
|
||||
rm -rf ~/.config/electron*
|
||||
fi
|
||||
|
||||
when: always
|
||||
|
||||
step-checkout-electron: &step-checkout-electron
|
||||
@@ -763,7 +780,7 @@ step-verify-mksnapshot: &step-verify-mksnapshot
|
||||
command: |
|
||||
if [ "$IS_ASAN" != "1" ]; then
|
||||
cd src
|
||||
if [ "$TARGET_ARCH" == "arm64" ] &&[ "`uname`" == "Darwin" ]; then
|
||||
if [ "$TARGET_ARCH" == "arm" ] || [ "$TARGET_ARCH" == "arm64" ]; then
|
||||
python electron/script/verify-mksnapshot.py --source-root "$PWD" --build-dir out/Default --snapshot-files-dir $PWD/cross-arch-snapshots
|
||||
else
|
||||
python electron/script/verify-mksnapshot.py --source-root "$PWD" --build-dir out/Default
|
||||
@@ -908,28 +925,6 @@ step-maybe-cross-arch-snapshot-store: &step-maybe-cross-arch-snapshot-store
|
||||
path: src/cross-arch-snapshots
|
||||
destination: cross-arch-snapshots
|
||||
|
||||
step-maybe-trigger-arm-test: &step-maybe-trigger-arm-test
|
||||
run:
|
||||
name: Trigger an arm test on VSTS if applicable
|
||||
command: |
|
||||
cd src
|
||||
# Only run for non-fork prs
|
||||
if [ "$TRIGGER_ARM_TEST" == "true" ] && [ -z "$CIRCLE_PR_NUMBER" ]; then
|
||||
#Trigger VSTS job, passing along CircleCI job number and branch to build
|
||||
if [ "`uname`" == "Darwin" ]; then
|
||||
if [ x"$MAS_BUILD" == x"true" ]; then
|
||||
export DEVOPS_BUILD="electron-mas-arm64-testing"
|
||||
else
|
||||
export DEVOPS_BUILD="electron-osx-arm64-testing"
|
||||
fi
|
||||
echo "Triggering $DEVOPS_BUILD build on Azure DevOps"
|
||||
node electron/script/release/ci-release-build.js --job=$DEVOPS_BUILD --ci=DevOps --armTest --circleBuildNum=$CIRCLE_BUILD_NUM $CIRCLE_BRANCH
|
||||
else
|
||||
echo "Triggering electron-$TARGET_ARCH-testing build on VSTS"
|
||||
node electron/script/release/ci-release-build.js --job=electron-$TARGET_ARCH-testing --ci=VSTS --armTest --circleBuildNum=$CIRCLE_BUILD_NUM $CIRCLE_BRANCH
|
||||
fi
|
||||
fi
|
||||
|
||||
step-maybe-generate-typescript-defs: &step-maybe-generate-typescript-defs
|
||||
run:
|
||||
name: Generate type declarations
|
||||
@@ -1344,7 +1339,7 @@ steps-tests: &steps-tests
|
||||
(cd electron && node script/yarn test --runners=main --trace-uncaught --enable-logging --files $(circleci tests glob spec-main/*-spec.ts | circleci tests split)) 2>&1 | $ASAN_SYMBOLIZE
|
||||
(cd electron && node script/yarn test --runners=remote --trace-uncaught --enable-logging --files $(circleci tests glob spec/*-spec.js | circleci tests split)) 2>&1 | $ASAN_SYMBOLIZE
|
||||
else
|
||||
if [ "$TARGET_ARCH" == "arm64" ] &&[ "`uname`" == "Darwin" ]; then
|
||||
if [ "$TARGET_ARCH" == "arm" ] || [ "$TARGET_ARCH" == "arm64" ]; then
|
||||
export ELECTRON_SKIP_NATIVE_MODULE_TESTS=true
|
||||
(cd electron && node script/yarn test --runners=main --trace-uncaught --enable-logging)
|
||||
(cd electron && node script/yarn test --runners=remote --trace-uncaught --enable-logging)
|
||||
@@ -1635,9 +1630,6 @@ commands:
|
||||
steps:
|
||||
- *step-save-out-cache
|
||||
|
||||
# Trigger tests on arm hardware if needed
|
||||
- *step-maybe-trigger-arm-test
|
||||
|
||||
- *step-maybe-notify-slack-failure
|
||||
|
||||
electron-publish:
|
||||
@@ -1981,7 +1973,7 @@ jobs:
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
|
||||
steps:
|
||||
- electron-build:
|
||||
persist: false
|
||||
persist: true
|
||||
checkout: true
|
||||
use-out-cache: false
|
||||
|
||||
@@ -2040,7 +2032,7 @@ jobs:
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
|
||||
steps:
|
||||
- electron-build:
|
||||
persist: false
|
||||
persist: true
|
||||
checkout: true
|
||||
use-out-cache: false
|
||||
|
||||
@@ -2437,6 +2429,24 @@ jobs:
|
||||
<<: *env-send-slack-notifications
|
||||
<<: *steps-verify-ffmpeg
|
||||
|
||||
linux-arm-testing-tests:
|
||||
executor: linux-arm
|
||||
environment:
|
||||
<<: *env-arm
|
||||
<<: *env-global
|
||||
<<: *env-headless-testing
|
||||
<<: *env-stack-dumping
|
||||
<<: *steps-tests
|
||||
|
||||
linux-arm64-testing-tests:
|
||||
executor: linux-arm64
|
||||
environment:
|
||||
<<: *env-arm64
|
||||
<<: *env-global
|
||||
<<: *env-headless-testing
|
||||
<<: *env-stack-dumping
|
||||
<<: *steps-tests
|
||||
|
||||
osx-testing-x64-tests:
|
||||
<<: *machine-mac-large
|
||||
environment:
|
||||
@@ -2689,8 +2699,23 @@ workflows:
|
||||
- linux-ia32-testing
|
||||
|
||||
- linux-arm-testing
|
||||
- linux-arm-testing-tests:
|
||||
filters:
|
||||
branches:
|
||||
# Do not run this on forked pull requests
|
||||
ignore: /pull\/[0-9]+/
|
||||
requires:
|
||||
- linux-arm-testing
|
||||
|
||||
- linux-arm64-testing
|
||||
- linux-arm64-testing-tests:
|
||||
filters:
|
||||
branches:
|
||||
# Do not run this on forked pull requests
|
||||
ignore: /pull\/[0-9]+/
|
||||
requires:
|
||||
- linux-arm64-testing
|
||||
|
||||
- linux-arm64-testing-gn-check:
|
||||
requires:
|
||||
- linux-checkout-fast
|
||||
|
||||
4
DEPS
4
DEPS
@@ -14,13 +14,13 @@ gclient_gn_args = [
|
||||
|
||||
vars = {
|
||||
'chromium_version':
|
||||
'91.0.4472.77',
|
||||
'91.0.4472.124',
|
||||
'node_version':
|
||||
'v14.16.0',
|
||||
'nan_version':
|
||||
'v2.14.2',
|
||||
'squirrel.mac_version':
|
||||
'cdc0729c8bf8576bfef18629186e1e9ecf1b0d9f',
|
||||
'0e5d146ba13101a1302d59ea6e6e0b3cace4ae38',
|
||||
|
||||
'pyyaml_version': '3.12',
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
13.1.1
|
||||
13.1.5
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from collections import OrderedDict
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
@@ -50,7 +51,7 @@ const volatile char kFuseWire[] = { /* sentinel */ {sentinel}, /* fuse_version *
|
||||
"""
|
||||
|
||||
with open(os.path.join(dir_path, "fuses.json5"), 'r') as f:
|
||||
fuse_defaults = json.loads(''.join(line for line in f.readlines() if not line.strip()[0] == "/"))
|
||||
fuse_defaults = json.loads(''.join(line for line in f.readlines() if not line.strip()[0] == "/"), object_pairs_hook=OrderedDict)
|
||||
|
||||
fuse_version = fuse_defaults['_version']
|
||||
del fuse_defaults['_version']
|
||||
|
||||
@@ -145,6 +145,8 @@ static_library("chrome") {
|
||||
|
||||
if (enable_color_chooser) {
|
||||
sources += [
|
||||
"//chrome/browser/devtools/devtools_eye_dropper.cc",
|
||||
"//chrome/browser/devtools/devtools_eye_dropper.h",
|
||||
"//chrome/browser/platform_util.cc",
|
||||
"//chrome/browser/platform_util.h",
|
||||
"//chrome/browser/ui/browser_dialogs.h",
|
||||
|
||||
@@ -339,7 +339,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
|
||||
more details.
|
||||
* `contextIsolation` Boolean (optional) - Whether to run Electron APIs and
|
||||
the specified `preload` script in a separate JavaScript context. Defaults
|
||||
to `false`. The context that the `preload` script runs in will only have
|
||||
to `true`. The context that the `preload` script runs in will only have
|
||||
access to its own dedicated `document` and `window` globals, as well as
|
||||
its own set of JavaScript builtins (`Array`, `Object`, `JSON`, etc.),
|
||||
which are all invisible to the loaded content. The Electron API will only
|
||||
@@ -351,8 +351,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
|
||||
context in the dev tools by selecting the 'Electron Isolated Context'
|
||||
entry in the combo box at the top of the Console tab.
|
||||
* `worldSafeExecuteJavaScript` Boolean (optional) - If true, values returned from `webFrame.executeJavaScript` will be sanitized to ensure JS values
|
||||
can't unsafely cross between worlds when using `contextIsolation`. The default
|
||||
is `false`. In Electron 12, the default will be changed to `true`. _Deprecated_
|
||||
can't unsafely cross between worlds when using `contextIsolation`. Defaults to `true`. _Deprecated_
|
||||
* `nativeWindowOpen` Boolean (optional) - Whether to use native
|
||||
`window.open()`. Defaults to `false`. Child windows will always have node
|
||||
integration disabled unless `nodeIntegrationInSubFrames` is true. **Note:** This option is currently
|
||||
|
||||
@@ -24,7 +24,7 @@ The `dialog` module has the following methods:
|
||||
* `buttonLabel` String (optional) - Custom label for the confirmation button, when
|
||||
left empty the default label will be used.
|
||||
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
||||
* `properties` String[] (optional) - Contains which features the dialog should
|
||||
* `properties` String[] (optional) - Contains which features the dialog should
|
||||
use. The following values are supported:
|
||||
* `openFile` - Allow files to be selected.
|
||||
* `openDirectory` - Allow directories to be selected.
|
||||
@@ -87,7 +87,7 @@ dialog.showOpenDialogSync(mainWindow, {
|
||||
* `buttonLabel` String (optional) - Custom label for the confirmation button, when
|
||||
left empty the default label will be used.
|
||||
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
||||
* `properties` String[] (optional) - Contains which features the dialog should
|
||||
* `properties` String[] (optional) - Contains which features the dialog should
|
||||
use. The following values are supported:
|
||||
* `openFile` - Allow files to be selected.
|
||||
* `openDirectory` - Allow directories to be selected.
|
||||
@@ -112,7 +112,7 @@ Returns `Promise<Object>` - Resolve with an object containing the following:
|
||||
|
||||
* `canceled` Boolean - whether or not the dialog was canceled.
|
||||
* `filePaths` String[] - An array of file paths chosen by the user. If the dialog is cancelled this will be an empty array.
|
||||
* `bookmarks` String[] (optional) _macOS_ _mas_ - An array matching the `filePaths` array of base64 encoded strings which contains security scoped bookmark data. `securityScopedBookmarks` must be enabled for this to be populated. (For return values, see [table here](#bookmarks-array).)
|
||||
* `bookmarks` String[] (optional) _macOS_ _mas_ - An array matching the `filePaths` array of base64 encoded strings which contains security scoped bookmark data. `securityScopedBookmarks` must be enabled for this to be populated. (For return values, see [table here](#bookmarks-array).)
|
||||
|
||||
The `browserWindow` argument allows the dialog to attach itself to a parent window, making it modal.
|
||||
|
||||
@@ -165,7 +165,7 @@ dialog.showOpenDialog(mainWindow, {
|
||||
displayed in front of the filename text field.
|
||||
* `showsTagField` Boolean (optional) _macOS_ - Show the tags input box,
|
||||
defaults to `true`.
|
||||
* `properties` String[] (optional)
|
||||
* `properties` String[] (optional)
|
||||
* `showHiddenFiles` - Show hidden files in dialog.
|
||||
* `createDirectory` _macOS_ - Allow creating new directories from dialog.
|
||||
* `treatPackageAsDirectory` _macOS_ - Treat packages, such as `.app` folders,
|
||||
@@ -195,7 +195,7 @@ The `filters` specifies an array of file types that can be displayed, see
|
||||
* `nameFieldLabel` String (optional) _macOS_ - Custom label for the text
|
||||
displayed in front of the filename text field.
|
||||
* `showsTagField` Boolean (optional) _macOS_ - Show the tags input box, defaults to `true`.
|
||||
* `properties` String[] (optional)
|
||||
* `properties` String[] (optional)
|
||||
* `showHiddenFiles` - Show hidden files in dialog.
|
||||
* `createDirectory` _macOS_ - Allow creating new directories from dialog.
|
||||
* `treatPackageAsDirectory` _macOS_ - Treat packages, such as `.app` folders,
|
||||
@@ -227,7 +227,7 @@ expanding and collapsing the dialog.
|
||||
`"warning"`. On Windows, `"question"` displays the same icon as `"info"`, unless
|
||||
you set an icon using the `"icon"` option. On macOS, both `"warning"` and
|
||||
`"error"` display the same warning icon.
|
||||
* `buttons` String[] (optional) - Array of texts for buttons. On Windows, an empty array
|
||||
* `buttons` String[] (optional) - Array of texts for buttons. On Windows, an empty array
|
||||
will result in one button labeled "OK".
|
||||
* `defaultId` Integer (optional) - Index of the button in the buttons array which will
|
||||
be selected by default when the message box opens.
|
||||
@@ -273,7 +273,7 @@ If `browserWindow` is not shown dialog will not be attached to it. In such case
|
||||
`"warning"`. On Windows, `"question"` displays the same icon as `"info"`, unless
|
||||
you set an icon using the `"icon"` option. On macOS, both `"warning"` and
|
||||
`"error"` display the same warning icon.
|
||||
* `buttons` String[] (optional) - Array of texts for buttons. On Windows, an empty array
|
||||
* `buttons` String[] (optional) - Array of texts for buttons. On Windows, an empty array
|
||||
will result in one button labeled "OK".
|
||||
* `defaultId` Integer (optional) - Index of the button in the buttons array which will
|
||||
be selected by default when the message box opens.
|
||||
|
||||
@@ -62,6 +62,11 @@ Sets the maximum and minimum pinch-to-zoom level.
|
||||
> webFrame.setVisualZoomLevelLimits(1, 3)
|
||||
> ```
|
||||
|
||||
> **NOTE**: Visual zoom only applies to pinch-to-zoom behavior. Cmd+/-/0 zoom shortcuts are
|
||||
> controlled by the 'zoomIn', 'zoomOut', and 'resetZoom' MenuItem roles in the application
|
||||
> Menu. To disable shortcuts, manually [define the Menu](./menu.md#examples) and omit zoom roles
|
||||
> from the definition.
|
||||
|
||||
### `webFrame.setSpellCheckProvider(language, provider)`
|
||||
|
||||
* `language` String
|
||||
|
||||
@@ -117,15 +117,18 @@ const mainWindow = new BrowserWindow({
|
||||
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||
if (url === 'about:blank') {
|
||||
return {
|
||||
frame: false,
|
||||
fullscreenable: false,
|
||||
backgroundColor: 'black',
|
||||
webPreferences: {
|
||||
preload: 'my-child-window-preload-script.js'
|
||||
action: 'allow',
|
||||
overrideBrowserWindowOptions: {
|
||||
frame: false,
|
||||
fullscreenable: false,
|
||||
backgroundColor: 'black',
|
||||
webPreferences: {
|
||||
preload: 'my-child-window-preload-script.js'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return { action: 'deny' }
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ function createWindow () {
|
||||
})
|
||||
|
||||
ipcMain.handle('dark-mode:system', () => {
|
||||
nativeTheme.themeSouce = 'system'
|
||||
nativeTheme.themeSource = 'system'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,9 @@
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<p>
|
||||
We are using node <script>document.write(process.versions.node)</script>,
|
||||
Chrome <script>document.write(process.versions.chrome)</script>,
|
||||
and Electron <script>document.write(process.versions.electron)</script>.
|
||||
</p>
|
||||
<p>After launching this application, you should see the system notification.</p>
|
||||
<p id="output">Click it to see the effect in this interface.</p>
|
||||
|
||||
<script src="renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,10 +3,7 @@ const { app, BrowserWindow } = require('electron')
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
height: 600
|
||||
})
|
||||
|
||||
win.loadFile('index.html')
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const myNotification = new Notification('Title', {
|
||||
body: 'Notification from the Renderer process'
|
||||
})
|
||||
const NOTIFICATION_TITLE = 'Title'
|
||||
const NOTIFICATION_BODY = 'Notification from the Renderer process. Click to log to console.'
|
||||
const CLICK_MESSAGE = 'Notification clicked!'
|
||||
|
||||
myNotification.onclick = () => {
|
||||
console.log('Notification clicked')
|
||||
}
|
||||
new Notification(NOTIFICATION_TITLE, { body: NOTIFICATION_BODY })
|
||||
.onclick = () => document.getElementById("output").innerText = CLICK_MESSAGE
|
||||
|
||||
@@ -2,15 +2,14 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<title>Recent Documents</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<h1>Recent Documents</h1>
|
||||
<p>
|
||||
We are using node <script>document.write(process.versions.node)</script>,
|
||||
Chrome <script>document.write(process.versions.chrome)</script>,
|
||||
and Electron <script>document.write(process.versions.electron)</script>.
|
||||
Right click on the app icon to see recent documents.
|
||||
You should see `recently-used.md` added to the list of recent files
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,17 +5,15 @@ const path = require('path')
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
height: 600
|
||||
})
|
||||
|
||||
win.loadFile('index.html')
|
||||
}
|
||||
|
||||
const fileName = 'recently-used.md'
|
||||
fs.writeFile(fileName, 'Lorem Ipsum', () => {
|
||||
app.addRecentDocument(path.join(process.cwd(), `${fileName}`))
|
||||
app.addRecentDocument(path.join(__dirname, fileName))
|
||||
})
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
<link rel="stylesheet" type="text/css" href="./styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<p>
|
||||
We are using node <script>document.write(process.versions.node)</script>,
|
||||
Chrome <script>document.write(process.versions.chrome)</script>,
|
||||
and Electron <script>document.write(process.versions.electron)</script>.
|
||||
Click on the title with the <pre>Command</pre> or <pre>Control</pre> key pressed.
|
||||
You should see a popup with the represented file at the top.
|
||||
</p>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,10 +4,7 @@ const os = require('os');
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
height: 600
|
||||
})
|
||||
|
||||
win.loadFile('index.html')
|
||||
|
||||
@@ -16,91 +16,12 @@
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/tray">
|
||||
full API documentation (opens in new window)
|
||||
full API documentation
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<button id="put-in-tray">View Demo</button>
|
||||
<span id="tray-countdown"></span>
|
||||
</div>
|
||||
<p>
|
||||
The demo button sends a message to the main process using the
|
||||
<code>ipc</code> module. In the main process the app is told to
|
||||
place an icon, with a context menu, in the tray.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In this example the tray icon can be removed by clicking 'Remove' in
|
||||
the context menu or selecting the demo button again.
|
||||
</p>
|
||||
<h5>Main Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const path = require('path')
|
||||
const {ipcMain, app, Menu, Tray} = require('electron')
|
||||
|
||||
let appIcon = null
|
||||
|
||||
ipcMain.on('put-in-tray', (event) => {
|
||||
const iconName = process.platform === 'win32' ? 'windows-icon.png' : 'iconTemplate.png'
|
||||
const iconPath = path.join(__dirname, iconName)
|
||||
appIcon = new Tray(iconPath)
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([{
|
||||
label: 'Remove',
|
||||
click: () => {
|
||||
event.sender.send('tray-removed')
|
||||
}
|
||||
}])
|
||||
|
||||
appIcon.setToolTip('Electron Demo in the tray.')
|
||||
appIcon.setContextMenu(contextMenu)
|
||||
})
|
||||
|
||||
ipcMain.on('remove-tray', () => {
|
||||
appIcon.destroy()
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (appIcon) appIcon.destroy()
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
<h5>Renderer Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const ipc = require('electron').ipcRenderer
|
||||
|
||||
const trayBtn = document.getElementById('put-in-tray')
|
||||
let trayOn = false
|
||||
|
||||
trayBtn.addEventListener('click', function (event) {
|
||||
if (trayOn) {
|
||||
trayOn = false
|
||||
document.getElementById('tray-countdown').innerHTML = ''
|
||||
ipc.send('remove-tray')
|
||||
} else {
|
||||
trayOn = true
|
||||
const message = 'Click demo again to remove.'
|
||||
document.getElementById('tray-countdown').innerHTML = message
|
||||
ipc.send('put-in-tray')
|
||||
}
|
||||
})
|
||||
// Tray removed from context menu on icon
|
||||
ipc.on('tray-removed', function () {
|
||||
ipc.send('remove-tray')
|
||||
trayOn = false
|
||||
document.getElementById('tray-countdown').innerHTML = ''
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
<div>
|
||||
<h2>ProTip</h2>
|
||||
<strong>Tray support in Linux.</strong>
|
||||
@@ -109,7 +30,7 @@ ipc.on('tray-removed', function () {
|
||||
will need to install <code>libappindicator1</code> to make the
|
||||
tray icon work. See the
|
||||
<a href="https://electronjs.org/docs/api/tray">
|
||||
full API documentation (opens in new window)
|
||||
full API documentation
|
||||
</a>
|
||||
for more details about using Tray on Linux.
|
||||
</p>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,35 +0,0 @@
|
||||
const { ipcRenderer, shell } = require('electron')
|
||||
|
||||
const trayBtn = document.getElementById('put-in-tray')
|
||||
const links = document.querySelectorAll('a[href]')
|
||||
|
||||
let trayOn = false
|
||||
|
||||
trayBtn.addEventListener('click', function (event) {
|
||||
if (trayOn) {
|
||||
trayOn = false
|
||||
document.getElementById('tray-countdown').innerHTML = ''
|
||||
ipcRenderer.send('remove-tray')
|
||||
} else {
|
||||
trayOn = true
|
||||
const message = 'Click demo again to remove.'
|
||||
document.getElementById('tray-countdown').innerHTML = message
|
||||
ipcRenderer.send('put-in-tray')
|
||||
}
|
||||
})
|
||||
// Tray removed from context menu on icon
|
||||
ipcRenderer.on('tray-removed', function () {
|
||||
ipcRenderer.send('remove-tray')
|
||||
trayOn = false
|
||||
document.getElementById('tray-countdown').innerHTML = ''
|
||||
})
|
||||
|
||||
Array.prototype.forEach.call(links, (link) => {
|
||||
const url = link.getAttribute('href')
|
||||
if (url.indexOf('http') === 0) {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
shell.openExternal(url)
|
||||
})
|
||||
}
|
||||
})
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 37 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 32 KiB |
0
docs/images/versioning-sketch-2.png
Executable file → Normal file
0
docs/images/versioning-sketch-2.png
Executable file → Normal file
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
@@ -159,7 +159,7 @@ function createWindow () {
|
||||
})
|
||||
|
||||
ipcMain.handle('dark-mode:system', () => {
|
||||
nativeTheme.themeSouce = 'system'
|
||||
nativeTheme.themeSource = 'system'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -135,14 +135,18 @@ a text file. A typical cache might look like this:
|
||||
|
||||
## Skip binary download
|
||||
|
||||
When installing the `electron` NPM package, it automatically downloads the electron binary.
|
||||
Under the hood, Electron's JavaScript API binds to a binary that contains its
|
||||
implementations. Because this binary is crucial to the function of any Electron app,
|
||||
it is downloaded by default in the `postinstall` step every time you install `electron`
|
||||
from the npm registry.
|
||||
|
||||
This can sometimes be unnecessary, e.g. in a CI environment, when testing another component.
|
||||
However, if you want to install your project's dependencies but don't need to use
|
||||
Electron functionality, you can set the `ELECTRON_SKIP_BINARY_DOWNLOAD` environment
|
||||
variable to prevent the binary from being downloaded. For instance, this feature can
|
||||
be useful in continuous integration environments when running unit tests that mock
|
||||
out the `electron` module.
|
||||
|
||||
To prevent the binary from being downloaded when you install all npm dependencies you can set the environment variable `ELECTRON_SKIP_BINARY_DOWNLOAD`.
|
||||
E.g.:
|
||||
|
||||
```sh
|
||||
```sh npm2yarn
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD=1 npm install
|
||||
```
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ To show notifications in the Main process, you need to use the
|
||||
|
||||
### Show notifications in the Renderer process
|
||||
|
||||
Assuming you have a working Electron application from the
|
||||
Starting with a working application from the
|
||||
[Quick Start Guide](quick-start.md), add the following line to the
|
||||
`index.html` file before the closing `</body>` tag:
|
||||
|
||||
@@ -26,26 +26,22 @@ Assuming you have a working Electron application from the
|
||||
<script src="renderer.js"></script>
|
||||
```
|
||||
|
||||
and add the `renderer.js` file:
|
||||
...and add the `renderer.js` file:
|
||||
|
||||
```javascript fiddle='docs/fiddles/features/notifications/renderer'
|
||||
const myNotification = new Notification('Title', {
|
||||
body: 'Notification from the Renderer process'
|
||||
})
|
||||
const NOTIFICATION_TITLE = 'Title'
|
||||
const NOTIFICATION_BODY = 'Notification from the Renderer process. Click to log to console.'
|
||||
const CLICK_MESSAGE = 'Notification clicked'
|
||||
|
||||
myNotification.onclick = () => {
|
||||
console.log('Notification clicked')
|
||||
}
|
||||
new Notification(NOTIFICATION_TITLE, { body: NOTIFICATION_BODY })
|
||||
.onclick = () => console.log(CLICK_MESSAGE)
|
||||
```
|
||||
|
||||
After launching the Electron application, you should see the notification:
|
||||
|
||||

|
||||
|
||||
If you open the Console and then click the notification, you will see the
|
||||
message that was generated after triggering the `onclick` event:
|
||||
|
||||

|
||||
Additionally, if you click on the notification, the DOM will update to show "Notification clicked!".
|
||||
|
||||
### Show notifications in the Main process
|
||||
|
||||
|
||||
@@ -120,9 +120,9 @@ file in the directory you executed it in. Both files can be analyzed using
|
||||
the Chrome Developer Tools, using the `Performance` and `Memory` tabs
|
||||
respectively.
|
||||
|
||||
![performance-cpu-prof]
|
||||
![Performance CPU Profile][performance-cpu-prof]
|
||||
|
||||
![performance-heap-prof]
|
||||
![Performance Heap Memory Profile][performance-heap-prof]
|
||||
|
||||
In this example, on the author's machine, we saw that loading `request` took
|
||||
almost half a second, whereas `node-fetch` took dramatically less memory
|
||||
|
||||
@@ -138,7 +138,7 @@ way to import Electron's content scripts.
|
||||
<!-- 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 runs within the renderer context, but are granted more
|
||||
begins loading. These scripts run within the renderer context, but are granted more
|
||||
privileges by having access to Node.js APIs.
|
||||
|
||||
A preload script can be attached to the main process in the `BrowserWindow` constructor's
|
||||
|
||||
@@ -13,39 +13,62 @@ __Application dock menu:__
|
||||
|
||||
![macOS Dock Menu][dock-menu-image]
|
||||
|
||||
To add a file to recent documents, you need to use the
|
||||
[app.addRecentDocument][addrecentdocument] API.
|
||||
|
||||
## Example
|
||||
|
||||
### Add an item to recent documents
|
||||
|
||||
Starting with a working application from the
|
||||
[Quick Start Guide](quick-start.md), add the following lines to the
|
||||
`main.js` file:
|
||||
### Managing recent documents
|
||||
|
||||
```javascript fiddle='docs/fiddles/features/recent-documents'
|
||||
const { app } = require('electron')
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
app.addRecentDocument('/Users/USERNAME/Desktop/work.type')
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600
|
||||
})
|
||||
|
||||
win.loadFile('index.html')
|
||||
}
|
||||
|
||||
const fileName = 'recently-used.md'
|
||||
fs.writeFile(fileName, 'Lorem Ipsum', () => {
|
||||
app.addRecentDocument(path.join(__dirname, fileName))
|
||||
})
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
app.clearRecentDocuments()
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### Adding a recent document
|
||||
|
||||
To add a file to recent documents, use the
|
||||
[app.addRecentDocument][addrecentdocument] API.
|
||||
|
||||
After launching the Electron application, right click the application icon.
|
||||
You should see the item you just added. In this guide, the item is a Markdown
|
||||
file located in the root of the project:
|
||||
In this guide, the item is a Markdown file located in the root of the project.
|
||||
You should see `recently-used.md` added to the list of recent files:
|
||||
|
||||

|
||||
|
||||
### Clear the list of recent documents
|
||||
#### Clearing the list of recent documents
|
||||
|
||||
To clear the list of recent documents, you need to use
|
||||
[app.clearRecentDocuments][clearrecentdocuments] API in the `main.js` file:
|
||||
|
||||
```javascript
|
||||
const { app } = require('electron')
|
||||
|
||||
app.clearRecentDocuments()
|
||||
```
|
||||
To clear the list of recent documents, use the
|
||||
[app.clearRecentDocuments][clearrecentdocuments] API.
|
||||
In this guide, the list of documents is cleared once all windows have been
|
||||
closed.
|
||||
|
||||
## Additional information
|
||||
|
||||
|
||||
@@ -20,23 +20,40 @@ To set the represented file of window, you can use the
|
||||
|
||||
## Example
|
||||
|
||||
Starting with a working application from the
|
||||
[Quick Start Guide](quick-start.md), add the following lines to the
|
||||
`main.js` file:
|
||||
|
||||
```javascript fiddle='docs/fiddles/features/represented-file'
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
const os = require('os');
|
||||
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600
|
||||
})
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const win = new BrowserWindow()
|
||||
|
||||
win.setRepresentedFilename('/etc/passwd')
|
||||
win.setRepresentedFilename(os.homedir())
|
||||
win.setDocumentEdited(true)
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
After launching the Electron application, click on the title with `Command` or
|
||||
`Control` key pressed. You should see a popup with the file you just defined:
|
||||
`Control` key pressed. You should see a popup with the represented file at the top.
|
||||
In this guide, this is the current user's home directory:
|
||||
|
||||

|
||||
|
||||
|
||||
83
docs/tutorial/tray.md
Normal file
83
docs/tutorial/tray.md
Normal file
@@ -0,0 +1,83 @@
|
||||
---
|
||||
title: Tray
|
||||
description: This guide will take you through the process of creating
|
||||
a Tray icon with its own context menu to the system's notification area.
|
||||
slug: tray
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
# Tray
|
||||
|
||||
## Overview
|
||||
|
||||
<!-- ✍ Update this section if you want to provide more details -->
|
||||
|
||||
This guide will take you through the process of creating a
|
||||
[Tray](https://www.electronjs.org/docs/api/tray) icon with
|
||||
its own context menu to the system's notification area.
|
||||
|
||||
On MacOS and Ubuntu, the Tray will be located on the top
|
||||
right corner of your screen, adjacent to your battery and wifi icons.
|
||||
On Windows, the Tray will usually be located in the bottom right corner.
|
||||
|
||||
## Example
|
||||
|
||||
### main.js
|
||||
|
||||
First we must import `app`, `Tray`, `Menu`, `nativeImage` from `electron`.
|
||||
|
||||
```js
|
||||
const { app, Tray, Menu, nativeImage } = require('electron')
|
||||
```
|
||||
|
||||
Next we will create our Tray. To do this, we will use a
|
||||
[`NativeImage`](https://www.electronjs.org/docs/api/native-image) icon,
|
||||
which can be created through any one of these
|
||||
[methods](https://www.electronjs.org/docs/api/native-image#methods).
|
||||
Note that we wrap our Tray creation code within an
|
||||
[`app.whenReady`](https://www.electronjs.org/docs/api/app#appwhenready)
|
||||
as we will need to wait for our electron app to finish initializing.
|
||||
|
||||
```js title='main.js'
|
||||
let tray
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const icon = nativeImage.createFromPath('path/to/asset.png')
|
||||
tray = new Tray(icon)
|
||||
|
||||
// note: your contextMenu, Tooltip and Title code will go here!
|
||||
})
|
||||
```
|
||||
|
||||
Great! Now we can start attaching a context menu to our Tray, like so:
|
||||
|
||||
```js
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{ label: 'Item1', type: 'radio' },
|
||||
{ label: 'Item2', type: 'radio' },
|
||||
{ label: 'Item3', type: 'radio', checked: true },
|
||||
{ label: 'Item4', type: 'radio' }
|
||||
])
|
||||
|
||||
tray.setContextMenu(contextMenu)
|
||||
```
|
||||
|
||||
The code above will create 4 separate radio-type items in the context menu.
|
||||
To read more about constructing native menus, click
|
||||
[here](https://www.electronjs.org/docs/api/menu#menubuildfromtemplatetemplate).
|
||||
|
||||
Finally, let's give our tray a tooltip and a title.
|
||||
|
||||
```js
|
||||
tray.setToolTip('This is my application')
|
||||
tray.setTitle('This is my title')
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
After you start your electron app, you should see the Tray residing
|
||||
in either the top or bottom right of your screen, depending on your
|
||||
operating system.
|
||||
|
||||
```fiddle docs/fiddles/native-ui/tray
|
||||
```
|
||||
@@ -490,63 +490,83 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
}
|
||||
};
|
||||
|
||||
function fsReadFileAsar (pathArgument: string, options: any, callback: any) {
|
||||
const pathInfo = splitPath(pathArgument);
|
||||
if (pathInfo.isAsar) {
|
||||
const { asarPath, filePath } = pathInfo;
|
||||
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = { encoding: null };
|
||||
} else if (typeof options === 'string') {
|
||||
options = { encoding: options };
|
||||
} else if (options === null || options === undefined) {
|
||||
options = { encoding: null };
|
||||
} else if (typeof options !== 'object') {
|
||||
throw new TypeError('Bad arguments');
|
||||
}
|
||||
|
||||
const { encoding } = options;
|
||||
const archive = getOrCreateArchive(asarPath);
|
||||
if (!archive) {
|
||||
const error = createError(AsarError.INVALID_ARCHIVE, { asarPath });
|
||||
nextTick(callback, [error]);
|
||||
return;
|
||||
}
|
||||
|
||||
const info = archive.getFileInfo(filePath);
|
||||
if (!info) {
|
||||
const error = createError(AsarError.NOT_FOUND, { asarPath, filePath });
|
||||
nextTick(callback, [error]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.size === 0) {
|
||||
nextTick(callback, [null, encoding ? '' : Buffer.alloc(0)]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.unpacked) {
|
||||
const realPath = archive.copyFileOut(filePath);
|
||||
return fs.readFile(realPath, options, callback);
|
||||
}
|
||||
|
||||
const buffer = Buffer.alloc(info.size);
|
||||
const fd = archive.getFd();
|
||||
if (!(fd >= 0)) {
|
||||
const error = createError(AsarError.NOT_FOUND, { asarPath, filePath });
|
||||
nextTick(callback, [error]);
|
||||
return;
|
||||
}
|
||||
|
||||
logASARAccess(asarPath, filePath, info.offset);
|
||||
fs.read(fd, buffer, 0, info.size, info.offset, (error: Error) => {
|
||||
callback(error, encoding ? buffer.toString(encoding) : buffer);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const { readFile } = fs;
|
||||
fs.readFile = function (pathArgument: string, options: any, callback: any) {
|
||||
const pathInfo = splitPath(pathArgument);
|
||||
if (!pathInfo.isAsar) return readFile.apply(this, arguments);
|
||||
const { asarPath, filePath } = pathInfo;
|
||||
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = { encoding: null };
|
||||
} else if (typeof options === 'string') {
|
||||
options = { encoding: options };
|
||||
} else if (options === null || options === undefined) {
|
||||
options = { encoding: null };
|
||||
} else if (typeof options !== 'object') {
|
||||
throw new TypeError('Bad arguments');
|
||||
if (!pathInfo.isAsar) {
|
||||
return readFile.apply(this, arguments);
|
||||
}
|
||||
|
||||
const { encoding } = options;
|
||||
const archive = getOrCreateArchive(asarPath);
|
||||
if (!archive) {
|
||||
const error = createError(AsarError.INVALID_ARCHIVE, { asarPath });
|
||||
nextTick(callback, [error]);
|
||||
return;
|
||||
}
|
||||
|
||||
const info = archive.getFileInfo(filePath);
|
||||
if (!info) {
|
||||
const error = createError(AsarError.NOT_FOUND, { asarPath, filePath });
|
||||
nextTick(callback, [error]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.size === 0) {
|
||||
nextTick(callback, [null, encoding ? '' : Buffer.alloc(0)]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.unpacked) {
|
||||
const realPath = archive.copyFileOut(filePath);
|
||||
return fs.readFile(realPath, options, callback);
|
||||
}
|
||||
|
||||
const buffer = Buffer.alloc(info.size);
|
||||
const fd = archive.getFd();
|
||||
if (!(fd >= 0)) {
|
||||
const error = createError(AsarError.NOT_FOUND, { asarPath, filePath });
|
||||
nextTick(callback, [error]);
|
||||
return;
|
||||
}
|
||||
|
||||
logASARAccess(asarPath, filePath, info.offset);
|
||||
fs.read(fd, buffer, 0, info.size, info.offset, (error: Error) => {
|
||||
callback(error, encoding ? buffer.toString(encoding) : buffer);
|
||||
});
|
||||
return fsReadFileAsar(pathArgument, options, callback);
|
||||
};
|
||||
|
||||
fs.promises.readFile = util.promisify(fs.readFile);
|
||||
const { readFile: readFilePromise } = fs.promises;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
fs.promises.readFile = function (pathArgument: string, options: any) {
|
||||
const pathInfo = splitPath(pathArgument);
|
||||
if (!pathInfo.isAsar) {
|
||||
return readFilePromise.apply(this, arguments);
|
||||
}
|
||||
|
||||
const p = util.promisify(fsReadFileAsar);
|
||||
return p(pathArgument, options);
|
||||
};
|
||||
|
||||
const { readFileSync } = fs;
|
||||
fs.readFileSync = function (pathArgument: string, options: any) {
|
||||
|
||||
@@ -76,6 +76,7 @@ ipcMainInternal.on(
|
||||
const referrer: Electron.Referrer = { url: '', policy: 'strict-origin-when-cross-origin' };
|
||||
const browserWindowOptions = event.sender._callWindowOpenHandler(event, { url, frameName, features, disposition: 'new-window', referrer });
|
||||
if (event.defaultPrevented) {
|
||||
event.returnValue = null;
|
||||
return;
|
||||
}
|
||||
const guest = openGuestWindow({
|
||||
|
||||
@@ -1,16 +1,32 @@
|
||||
import { internalContextBridge } from '@electron/internal/renderer/api/context-bridge';
|
||||
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
|
||||
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
|
||||
import { webFrame } from 'electron/renderer';
|
||||
import { IPC_MESSAGES } from '../common/ipc-messages';
|
||||
|
||||
const { contextIsolationEnabled } = internalContextBridge;
|
||||
|
||||
/* Corrects for some Inspector adaptations needed in Electron.
|
||||
* 1) Use menu API to show context menu.
|
||||
* 2) Correct for Chromium returning undefined for filesystem.
|
||||
* 3) Use dialog API to override file chooser dialog.
|
||||
*/
|
||||
window.onload = function () {
|
||||
// Use menu API to show context menu.
|
||||
window.InspectorFrontendHost!.showContextMenuAtPoint = createMenu;
|
||||
|
||||
// correct for Chromium returning undefined for filesystem
|
||||
window.Persistence!.FileSystemWorkspaceBinding.completeURL = completeURL;
|
||||
|
||||
// Use dialog API to override file chooser dialog.
|
||||
window.UI!.createFileSelectorElement = createFileSelectorElement;
|
||||
if (contextIsolationEnabled) {
|
||||
internalContextBridge.overrideGlobalValueFromIsolatedWorld([
|
||||
'InspectorFrontendHost', 'showContextMenuAtPoint'
|
||||
], createMenu);
|
||||
internalContextBridge.overrideGlobalValueFromIsolatedWorld([
|
||||
'Persistence', 'FileSystemWorkspaceBinding', 'completeURL'
|
||||
], completeURL);
|
||||
internalContextBridge.overrideGlobalValueFromIsolatedWorld([
|
||||
'UI', 'createFileSelectorElement'
|
||||
], createFileSelectorElement);
|
||||
} else {
|
||||
window.InspectorFrontendHost!.showContextMenuAtPoint = createMenu;
|
||||
window.Persistence!.FileSystemWorkspaceBinding.completeURL = completeURL;
|
||||
window.UI!.createFileSelectorElement = createFileSelectorElement;
|
||||
}
|
||||
};
|
||||
|
||||
// Extra / is needed as a result of MacOS requiring absolute paths
|
||||
@@ -36,9 +52,10 @@ const createMenu = function (x: number, y: number, items: ContextMenuItem[]) {
|
||||
const isEditMenu = useEditMenuItems(x, y, items);
|
||||
ipcRendererInternal.invoke<number>(IPC_MESSAGES.INSPECTOR_CONTEXT_MENU, items, isEditMenu).then(id => {
|
||||
if (typeof id === 'number') {
|
||||
window.DevToolsAPI!.contextMenuItemSelected(id);
|
||||
webFrame.executeJavaScript(`window.DevToolsAPI.contextMenuItemSelected(${JSON.stringify(id)})`);
|
||||
}
|
||||
window.DevToolsAPI!.contextMenuCleared();
|
||||
|
||||
webFrame.executeJavaScript('window.DevToolsAPI.contextMenuCleared()');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -270,9 +270,7 @@ const warnAboutAllowedPopups = function () {
|
||||
// Logs a warning message about the remote module
|
||||
|
||||
const warnAboutRemoteModuleWithRemoteContent = function (webPreferences?: Electron.WebPreferences) {
|
||||
if (!webPreferences || isLocalhost()) return;
|
||||
const remoteModuleEnabled = webPreferences.enableRemoteModule != null ? !!webPreferences.enableRemoteModule : true;
|
||||
if (!remoteModuleEnabled) return;
|
||||
if (!webPreferences || !webPreferences.enableRemoteModule || isLocalhost()) return;
|
||||
|
||||
if (getIsRemoteProtocol()) {
|
||||
const warning = `This renderer process has "enableRemoteModule" enabled
|
||||
@@ -298,7 +296,9 @@ const logSecurityWarnings = function (
|
||||
warnAboutEnableBlinkFeatures(webPreferences);
|
||||
warnAboutInsecureCSP();
|
||||
warnAboutAllowedPopups();
|
||||
warnAboutRemoteModuleWithRemoteContent(webPreferences);
|
||||
if (BUILDFLAG(ENABLE_REMOTE_MODULE)) {
|
||||
warnAboutRemoteModuleWithRemoteContent(webPreferences);
|
||||
}
|
||||
};
|
||||
|
||||
const getWebPreferences = async function () {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"version": "13.1.1",
|
||||
"version": "13.1.5",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -113,3 +113,4 @@ build_libc_as_static_library.patch
|
||||
cherry-pick-3299d70b7d0f.patch
|
||||
support_runtime_configurable_key_storage_on_linux_os_crypto.patch
|
||||
make_keychain_service_account_optionally_configurable_at_runtime.patch
|
||||
don_t_run_pcscan_notifythreadcreated_if_pcscan_is_disabled.patch
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Cheng Zhao <zcbenz@gmail.com>
|
||||
Date: Thu, 4 Oct 2018 14:57:02 -0700
|
||||
Subject: accelerator.patch
|
||||
Subject: fix: improve shortcut text of Accelerator
|
||||
|
||||
This patch makes three changes to Accelerator::GetShortcutText to improve shortcut display text in menus:
|
||||
|
||||
1. Ctrl-Alt-<Key> accelerators show as Ctrl-Alt-<Key> instead of as Ctrl-<Key>
|
||||
2. F2-F24 accelerators show up as such
|
||||
3. Ctrl-Shift-= should show as Ctrl-+
|
||||
3. Ctrl-Shift-= and Ctrl-Plus show up as such
|
||||
|
||||
diff --git a/ui/base/accelerators/accelerator.cc b/ui/base/accelerators/accelerator.cc
|
||||
index c44f3d3752025bd3f11db790a97a48e8ba856034..8e0c1446315823a391614b19aa2c4ba2e5faed0d 100644
|
||||
index c44f3d3752025bd3f11db790a97a48e8ba856034..8b1859b18aa7ebcecf4c2a90b4ced0186d258ddc 100644
|
||||
--- a/ui/base/accelerators/accelerator.cc
|
||||
+++ b/ui/base/accelerators/accelerator.cc
|
||||
@@ -11,6 +11,7 @@
|
||||
@@ -21,61 +21,39 @@ index c44f3d3752025bd3f11db790a97a48e8ba856034..8e0c1446315823a391614b19aa2c4ba2
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "build/build_config.h"
|
||||
#include "build/chromeos_buildflags.h"
|
||||
@@ -27,9 +28,7 @@
|
||||
#include <windows.h>
|
||||
@@ -209,6 +210,11 @@ std::u16string Accelerator::GetShortcutText() const {
|
||||
#endif
|
||||
|
||||
-#if !defined(OS_WIN) && (defined(USE_AURA) || defined(OS_MAC))
|
||||
#include "ui/events/keycodes/keyboard_code_conversion.h"
|
||||
-#endif
|
||||
|
||||
#if BUILDFLAG(IS_CHROMEOS_ASH)
|
||||
#include "ui/base/ui_base_features.h"
|
||||
@@ -208,7 +207,15 @@ std::u16string Accelerator::GetShortcutText() const {
|
||||
shortcut = KeyCodeToName();
|
||||
#endif
|
||||
|
||||
+ unsigned int flags = 0;
|
||||
if (shortcut.empty()) {
|
||||
+ const uint16_t c = DomCodeToUsLayoutCharacter(
|
||||
+ UsLayoutKeyboardCodeToDomCode(key_code_), flags);
|
||||
+ if (c != 0) {
|
||||
+ shortcut =
|
||||
+ static_cast<std::u16string::value_type>(
|
||||
+ base::ToUpperASCII(static_cast<char16_t>(c)));
|
||||
+ }
|
||||
+ // When a shifted char is explicitly specified, for example Ctrl+Plus,
|
||||
+ // use the shifted char directly.
|
||||
+ if (shifted_char) {
|
||||
+ shortcut += *shifted_char;
|
||||
+ } else {
|
||||
#if defined(OS_WIN)
|
||||
// Our fallback is to try translate the key code to a regular character
|
||||
// unless it is one of digits (VK_0 to VK_9). Some keyboard
|
||||
@@ -217,21 +224,14 @@ std::u16string Accelerator::GetShortcutText() const {
|
||||
// accent' for '0'). For display in the menu (e.g. Ctrl-0 for the
|
||||
// default zoom level), we leave VK_[0-9] alone without translation.
|
||||
wchar_t key;
|
||||
- if (base::IsAsciiDigit(key_code_))
|
||||
+ if (base::IsAsciiDigit(key_code_)) {
|
||||
key = static_cast<wchar_t>(key_code_);
|
||||
- else
|
||||
- key = LOWORD(::MapVirtualKeyW(key_code_, MAPVK_VK_TO_CHAR));
|
||||
- // If there is no translation for the given |key_code_| (e.g.
|
||||
- // VKEY_UNKNOWN), |::MapVirtualKeyW| returns 0.
|
||||
- if (key != 0)
|
||||
- shortcut += key;
|
||||
-#elif defined(USE_AURA) || defined(OS_MAC) || defined(OS_ANDROID)
|
||||
- const uint16_t c = DomCodeToUsLayoutCharacter(
|
||||
- UsLayoutKeyboardCodeToDomCode(key_code_), false);
|
||||
- if (c != 0)
|
||||
- shortcut +=
|
||||
- static_cast<std::u16string::value_type>(base::ToUpperASCII(c));
|
||||
+ shortcut = key;
|
||||
+ }
|
||||
@@ -232,6 +238,10 @@ std::u16string Accelerator::GetShortcutText() const {
|
||||
shortcut +=
|
||||
static_cast<std::u16string::value_type>(base::ToUpperASCII(c));
|
||||
#endif
|
||||
+ }
|
||||
+ if (key_code_ > VKEY_F1 && key_code_ <= VKEY_F24)
|
||||
+ shortcut = base::UTF8ToUTF16(
|
||||
+ base::StringPrintf("F%d", key_code_ - VKEY_F1 + 1));
|
||||
}
|
||||
|
||||
#if defined(OS_MAC)
|
||||
@@ -427,7 +427,7 @@ std::u16string Accelerator::ApplyLongFormModifiers(
|
||||
@@ -419,7 +429,7 @@ std::u16string Accelerator::ApplyLongFormModifiers(
|
||||
const std::u16string& shortcut) const {
|
||||
std::u16string result = shortcut;
|
||||
|
||||
- if (IsShiftDown())
|
||||
+ if (!shifted_char && IsShiftDown())
|
||||
result = ApplyModifierToAcceleratorString(result, IDS_APP_SHIFT_KEY);
|
||||
|
||||
// Note that we use 'else-if' in order to avoid using Ctrl+Alt as a shortcut.
|
||||
@@ -427,7 +437,7 @@ std::u16string Accelerator::ApplyLongFormModifiers(
|
||||
// more information.
|
||||
if (IsCtrlDown())
|
||||
result = ApplyModifierToAcceleratorString(result, IDS_APP_CTRL_KEY);
|
||||
@@ -84,3 +62,24 @@ index c44f3d3752025bd3f11db790a97a48e8ba856034..8e0c1446315823a391614b19aa2c4ba2
|
||||
result = ApplyModifierToAcceleratorString(result, IDS_APP_ALT_KEY);
|
||||
|
||||
if (IsCmdDown()) {
|
||||
diff --git a/ui/base/accelerators/accelerator.h b/ui/base/accelerators/accelerator.h
|
||||
index 62eacbe23f949cf62b86a775d331459ae3934048..008b753809f34b72d0c1de46916c0d36eb90a8f5 100644
|
||||
--- a/ui/base/accelerators/accelerator.h
|
||||
+++ b/ui/base/accelerators/accelerator.h
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "base/component_export.h"
|
||||
+#include "base/optional.h"
|
||||
#include "base/time/time.h"
|
||||
#include "build/build_config.h"
|
||||
#include "ui/events/event_constants.h"
|
||||
@@ -100,6 +101,8 @@ class COMPONENT_EXPORT(UI_BASE) Accelerator {
|
||||
return interrupted_by_mouse_event_;
|
||||
}
|
||||
|
||||
+ base::Optional<char16_t> shifted_char;
|
||||
+
|
||||
private:
|
||||
std::u16string ApplyLongFormModifiers(const std::u16string& shortcut) const;
|
||||
std::u16string ApplyShortFormModifiers(const std::u16string& shortcut) const;
|
||||
|
||||
@@ -9,10 +9,10 @@ potentially prevent a window from being created.
|
||||
TODO(loc): this patch is currently broken.
|
||||
|
||||
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
index 08a3eb686360e7a53cca75437cbe5f4d15cb9a17..78d3287ef7a708f44bd8ef0290618ef781f09d9c 100644
|
||||
index 7f7e6adf57128f1e8b0890aa14102fe4db452cb2..59d855ec17efe9117ee9bca5074b1ab28dfb40fa 100644
|
||||
--- a/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
@@ -5440,6 +5440,7 @@ void RenderFrameHostImpl::CreateNewWindow(
|
||||
@@ -5430,6 +5430,7 @@ void RenderFrameHostImpl::CreateNewWindow(
|
||||
last_committed_origin_, params->window_container_type,
|
||||
params->target_url, params->referrer.To<Referrer>(),
|
||||
params->frame_name, params->disposition, *params->features,
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: John Kleinschmidt <jkleinsc@electronjs.org>
|
||||
Date: Wed, 16 Jun 2021 11:30:28 -0400
|
||||
Subject: Don't run PCScan functions if PCScan is disabled
|
||||
|
||||
PCScan should not be invoked if PCScan is disabled. Upstreamed at https://chromium-review.googlesource.com/c/chromium/src/+/2916657.
|
||||
|
||||
diff --git a/base/allocator/partition_allocator/memory_reclaimer.cc b/base/allocator/partition_allocator/memory_reclaimer.cc
|
||||
index 76927a8939b2ead660cf25cecb67a4fa095d98ec..7e922a28b8fa67bb971871c0e54ae59d9086db22 100644
|
||||
--- a/base/allocator/partition_allocator/memory_reclaimer.cc
|
||||
+++ b/base/allocator/partition_allocator/memory_reclaimer.cc
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "base/allocator/partition_allocator/partition_alloc.h"
|
||||
#include "base/allocator/partition_allocator/partition_alloc_check.h"
|
||||
#include "base/allocator/partition_allocator/partition_alloc_config.h"
|
||||
+#include "base/allocator/partition_allocator/partition_alloc_features.h"
|
||||
#include "base/allocator/partition_allocator/starscan/pcscan.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/location.h"
|
||||
@@ -121,6 +122,7 @@ void PartitionAllocMemoryReclaimer::Reclaim(int flags) {
|
||||
AutoLock lock(lock_); // Has to protect from concurrent (Un)Register calls.
|
||||
TRACE_EVENT0("base", "PartitionAllocMemoryReclaimer::Reclaim()");
|
||||
|
||||
+#if PA_ALLOW_PCSCAN
|
||||
// PCScan quarantines freed slots. Trigger the scan first to let it call
|
||||
// FreeNoHooksImmediate on slots that pass the quarantine.
|
||||
//
|
||||
@@ -130,13 +132,14 @@ void PartitionAllocMemoryReclaimer::Reclaim(int flags) {
|
||||
//
|
||||
// Lastly decommit empty slot spans and lastly try to discard unused pages at
|
||||
// the end of the remaining active slots.
|
||||
- {
|
||||
+ if (base::FeatureList::IsEnabled(base::features::kPartitionAllocPCScan)) {
|
||||
using PCScan = internal::PCScan;
|
||||
const auto invocation_mode = flags & PartitionPurgeAggressiveReclaim
|
||||
? PCScan::InvocationMode::kForcedBlocking
|
||||
: PCScan::InvocationMode::kBlocking;
|
||||
PCScan::Instance().PerformScanIfNeeded(invocation_mode);
|
||||
}
|
||||
+#endif
|
||||
|
||||
#if defined(PA_THREAD_CACHE_SUPPORTED)
|
||||
// Don't completely empty the thread cache outside of low memory situations,
|
||||
@@ -449,7 +449,7 @@ index 85073b2f5d58d3e071fb6ef30598973b4d00eda8..c81c820d61da3c7d1cfd2c516147c954
|
||||
|
||||
NSString * const SQRLUpdaterErrorDomain = @"SQRLUpdaterErrorDomain";
|
||||
diff --git a/Squirrel/SQRLZipArchiver.m b/Squirrel/SQRLZipArchiver.m
|
||||
index f84127f642516078249925953e97621909265deb..478509cdd528db4fcfa340c6f93fa58a446957e6 100644
|
||||
index cbc8fb61c66184c1d76e8b9f52a89292b4e9939c..d0f20f022a4ff392b596bcf0be96ab3e3c835285 100644
|
||||
--- a/Squirrel/SQRLZipArchiver.m
|
||||
+++ b/Squirrel/SQRLZipArchiver.m
|
||||
@@ -7,8 +7,8 @@
|
||||
@@ -508,7 +508,7 @@ index 0000000000000000000000000000000000000000..bdfaf95f3eca65b3e0831db1b66f651d
|
||||
+}
|
||||
diff --git a/build/xcrun.py b/build/xcrun.py
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..20d0cdb51cc933f56b7a7193c195457e82500870
|
||||
index 0000000000000000000000000000000000000000..18ac587f80441106405d00fafd9ee1f25b147772
|
||||
--- /dev/null
|
||||
+++ b/build/xcrun.py
|
||||
@@ -0,0 +1,14 @@
|
||||
@@ -524,7 +524,7 @@ index 0000000000000000000000000000000000000000..20d0cdb51cc933f56b7a7193c195457e
|
||||
+try:
|
||||
+ subprocess.check_output(args, stderr=subprocess.STDOUT)
|
||||
+except subprocess.CalledProcessError as e:
|
||||
+ print("xcrun script '" + sys.argv[2] + "' failed with code '" + str(e.returncode) + "':\n" + e.output)
|
||||
+ print("xcrun script '" + ' '.join(sys.argv[1:]) + "' failed with code '" + str(e.returncode) + "':\n" + e.output)
|
||||
+ sys.exit(e.returncode)
|
||||
diff --git a/filenames.gni b/filenames.gni
|
||||
new file mode 100644
|
||||
|
||||
@@ -6,10 +6,10 @@ Subject: fix: ensure that self is retained until the RACSignal is complete
|
||||
Looks like the clang that Chromium uses is slightly smarter with ARC than whatever Squirrel was built with previously. We now need to keep a reference to self to keep it alive into the "then" of the RACSignal.
|
||||
|
||||
diff --git a/Squirrel/SQRLZipArchiver.m b/Squirrel/SQRLZipArchiver.m
|
||||
index 478509cdd528db4fcfa340c6f93fa58a446957e6..7c279bf73c368453bff4f922d76908c06dc378cd 100644
|
||||
index d0f20f022a4ff392b596bcf0be96ab3e3c835285..68f5dac8e553638f41306956df9d38eeda18f8f2 100644
|
||||
--- a/Squirrel/SQRLZipArchiver.m
|
||||
+++ b/Squirrel/SQRLZipArchiver.m
|
||||
@@ -134,7 +134,7 @@ - (RACSignal *)launchWithArguments:(NSArray *)arguments {
|
||||
@@ -135,7 +135,7 @@ - (RACSignal *)launchWithArguments:(NSArray *)arguments {
|
||||
return [RACSignal
|
||||
zip:@[ self.taskTerminated, self.standardErrorData ]
|
||||
reduce:^(NSNumber *exitStatus, NSData *errorData) {
|
||||
|
||||
@@ -19,10 +19,10 @@ index 50173de3f145b4febcc29c03080306c137918527..1ac347ede7e51559917f09442ac4824f
|
||||
isolate->default_microtask_queue()->PerformCheckpoint(this);
|
||||
}
|
||||
diff --git a/src/heap/heap.cc b/src/heap/heap.cc
|
||||
index 823d37fd02d0169f0cb2785f9aa3395c5970711e..db6c29b72dd45f625b9876dab0e68ee56c9fae66 100644
|
||||
index af55137e1f6026f5e97f7b496ee71b78ddeb665b..582fc6302bddc736d71cb650d45d6bf9deafa9b2 100644
|
||||
--- a/src/heap/heap.cc
|
||||
+++ b/src/heap/heap.cc
|
||||
@@ -5600,9 +5600,9 @@ void Heap::TearDown() {
|
||||
@@ -5607,9 +5607,9 @@ void Heap::TearDown() {
|
||||
void Heap::AddGCPrologueCallback(v8::Isolate::GCCallbackWithData callback,
|
||||
GCType gc_type, void* data) {
|
||||
DCHECK_NOT_NULL(callback);
|
||||
|
||||
@@ -230,6 +230,7 @@ async function callAppVeyor (targetBranch, job, options) {
|
||||
accountName: 'electron-bot',
|
||||
projectSlug: appVeyorJobs[job],
|
||||
branch: targetBranch,
|
||||
commitId: options.commit || undefined,
|
||||
environmentVariables
|
||||
}),
|
||||
method: 'POST'
|
||||
@@ -365,7 +366,7 @@ if (require.main === module) {
|
||||
if (args._.length < 1) {
|
||||
console.log(`Trigger CI to build release builds of electron.
|
||||
Usage: ci-release-build.js [--job=CI_JOB_NAME] [--ci=CircleCI|AppVeyor|VSTS|DevOps]
|
||||
[--ghRelease] [--armTest] [--circleBuildNum=xxx] [--appveyorJobId=xxx] TARGET_BRANCH
|
||||
[--ghRelease] [--armTest] [--circleBuildNum=xxx] [--appveyorJobId=xxx] [--commit=sha] TARGET_BRANCH
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
@@ -88,9 +88,9 @@ bool SubprocessNeedsResourceBundle(const std::string& process_type) {
|
||||
#if defined(OS_MAC)
|
||||
// Mac needs them too for scrollbar related images and for sandbox
|
||||
// profiles.
|
||||
process_type == ::switches::kPpapiPluginProcess ||
|
||||
process_type == ::switches::kGpuProcess ||
|
||||
#endif
|
||||
process_type == ::switches::kPpapiPluginProcess ||
|
||||
process_type == ::switches::kRendererProcess ||
|
||||
process_type == ::switches::kUtilityProcess;
|
||||
}
|
||||
|
||||
@@ -536,10 +536,16 @@ void OnClientCertificateSelected(
|
||||
if (!certs.empty()) {
|
||||
scoped_refptr<net::X509Certificate> cert(certs[0].get());
|
||||
for (auto& identity : *identities) {
|
||||
if (cert->EqualsExcludingChain(identity->certificate())) {
|
||||
scoped_refptr<net::X509Certificate> identity_cert =
|
||||
identity->certificate();
|
||||
// Since |cert| was recreated from |data|, it won't include any
|
||||
// intermediates. That's fine for checking equality, but once a match is
|
||||
// found then |identity_cert| should be used since it will include the
|
||||
// intermediates which would otherwise be lost.
|
||||
if (cert->EqualsExcludingChain(identity_cert.get())) {
|
||||
net::ClientCertIdentity::SelfOwningAcquirePrivateKey(
|
||||
std::move(identity),
|
||||
base::BindRepeating(&GotPrivateKey, delegate, std::move(cert)));
|
||||
std::move(identity), base::BindRepeating(&GotPrivateKey, delegate,
|
||||
std::move(identity_cert)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -867,6 +867,10 @@ void BaseWindow::SetVibrancy(v8::Isolate* isolate, v8::Local<v8::Value> value) {
|
||||
}
|
||||
|
||||
#if defined(OS_MAC)
|
||||
std::string BaseWindow::GetAlwaysOnTopLevel() {
|
||||
return window_->GetAlwaysOnTopLevel();
|
||||
}
|
||||
|
||||
void BaseWindow::SetWindowButtonVisibility(bool visible) {
|
||||
window_->SetWindowButtonVisibility(visible);
|
||||
}
|
||||
@@ -1253,6 +1257,7 @@ void BaseWindow::BuildPrototype(v8::Isolate* isolate,
|
||||
.SetMethod("isVisibleOnAllWorkspaces",
|
||||
&BaseWindow::IsVisibleOnAllWorkspaces)
|
||||
#if defined(OS_MAC)
|
||||
.SetMethod("_getAlwaysOnTopLevel", &BaseWindow::GetAlwaysOnTopLevel)
|
||||
.SetMethod("setAutoHideCursor", &BaseWindow::SetAutoHideCursor)
|
||||
#endif
|
||||
.SetMethod("setVibrancy", &BaseWindow::SetVibrancy)
|
||||
|
||||
@@ -191,6 +191,7 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
|
||||
virtual void SetVibrancy(v8::Isolate* isolate, v8::Local<v8::Value> value);
|
||||
|
||||
#if defined(OS_MAC)
|
||||
std::string GetAlwaysOnTopLevel();
|
||||
void SetWindowButtonVisibility(bool visible);
|
||||
bool GetWindowButtonVisibility() const;
|
||||
void SetTrafficLightPosition(const gfx::Point& position);
|
||||
|
||||
@@ -393,6 +393,10 @@ void BrowserWindow::ResetBrowserViews() {
|
||||
#endif
|
||||
}
|
||||
|
||||
void BrowserWindow::OnDevToolsResized() {
|
||||
UpdateDraggableRegions(draggable_regions_);
|
||||
}
|
||||
|
||||
void BrowserWindow::SetVibrancy(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> value) {
|
||||
std::string type = gin::V8ToString(isolate, value);
|
||||
|
||||
@@ -63,9 +63,7 @@ class BrowserWindow : public BaseWindow,
|
||||
void OnActivateContents() override;
|
||||
void OnPageTitleUpdated(const std::u16string& title,
|
||||
bool explicit_set) override;
|
||||
#if defined(OS_MAC)
|
||||
void OnDevToolsResized() override;
|
||||
#endif
|
||||
|
||||
// NativeWindowObserver:
|
||||
void RequestPreferredWidth(int* width) override;
|
||||
@@ -121,9 +119,7 @@ class BrowserWindow : public BaseWindow,
|
||||
// it should be cancelled when we can prove that the window is responsive.
|
||||
base::CancelableClosure window_unresponsive_closure_;
|
||||
|
||||
#if defined(OS_MAC)
|
||||
std::vector<mojom::DraggableRegionPtr> draggable_regions_;
|
||||
#endif
|
||||
|
||||
v8::Global<v8::Value> web_contents_;
|
||||
base::WeakPtr<api::WebContents> api_web_contents_;
|
||||
|
||||
@@ -37,10 +37,6 @@ void BrowserWindow::OverrideNSWindowContentView(
|
||||
[contentView viewDidMoveToWindow];
|
||||
}
|
||||
|
||||
void BrowserWindow::OnDevToolsResized() {
|
||||
UpdateDraggableRegions(draggable_regions_);
|
||||
}
|
||||
|
||||
void BrowserWindow::UpdateDraggableRegions(
|
||||
const std::vector<mojom::DraggableRegionPtr>& regions) {
|
||||
if (window_->has_frame())
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "shell/browser/api/electron_api_browser_window.h"
|
||||
|
||||
#include "shell/browser/native_window_views.h"
|
||||
#include "ui/aura/window.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
@@ -14,8 +15,20 @@ void BrowserWindow::UpdateDraggableRegions(
|
||||
const std::vector<mojom::DraggableRegionPtr>& regions) {
|
||||
if (window_->has_frame())
|
||||
return;
|
||||
|
||||
if (&draggable_regions_ != ®ions) {
|
||||
auto const offset =
|
||||
web_contents()->GetNativeView()->GetBoundsInRootWindow();
|
||||
auto snapped_regions = mojo::Clone(regions);
|
||||
for (auto& snapped_region : snapped_regions) {
|
||||
snapped_region->bounds.Offset(offset.x(), offset.y());
|
||||
}
|
||||
|
||||
draggable_regions_ = mojo::Clone(snapped_regions);
|
||||
}
|
||||
|
||||
static_cast<NativeWindowViews*>(window_.get())
|
||||
->UpdateDraggableRegions(regions);
|
||||
->UpdateDraggableRegions(draggable_regions_);
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
|
||||
@@ -293,6 +293,7 @@ v8::Local<v8::Promise> Cookies::Set(v8::Isolate* isolate,
|
||||
const std::string* url_string = details.FindStringKey("url");
|
||||
if (!url_string) {
|
||||
promise.RejectWithErrorMessage("Missing required option 'url'");
|
||||
return handle;
|
||||
}
|
||||
const std::string* name = details.FindStringKey("name");
|
||||
const std::string* value = details.FindStringKey("value");
|
||||
|
||||
@@ -236,11 +236,13 @@ std::u16string Menu::GetToolTipAt(int index) const {
|
||||
return model_->GetToolTipAt(index);
|
||||
}
|
||||
|
||||
std::u16string Menu::GetAcceleratorTextAt(int index) const {
|
||||
#if DCHECK_IS_ON()
|
||||
std::u16string Menu::GetAcceleratorTextAtForTesting(int index) const {
|
||||
ui::Accelerator accelerator;
|
||||
model_->GetAcceleratorAtWithParams(index, true, &accelerator);
|
||||
return accelerator.GetShortcutText();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Menu::IsItemCheckedAt(int index) const {
|
||||
return model_->IsItemCheckedAt(index);
|
||||
@@ -289,13 +291,15 @@ v8::Local<v8::ObjectTemplate> Menu::FillObjectTemplate(
|
||||
.SetMethod("getLabelAt", &Menu::GetLabelAt)
|
||||
.SetMethod("getSublabelAt", &Menu::GetSublabelAt)
|
||||
.SetMethod("getToolTipAt", &Menu::GetToolTipAt)
|
||||
.SetMethod("getAcceleratorTextAt", &Menu::GetAcceleratorTextAt)
|
||||
.SetMethod("isItemCheckedAt", &Menu::IsItemCheckedAt)
|
||||
.SetMethod("isEnabledAt", &Menu::IsEnabledAt)
|
||||
.SetMethod("worksWhenHiddenAt", &Menu::WorksWhenHiddenAt)
|
||||
.SetMethod("isVisibleAt", &Menu::IsVisibleAt)
|
||||
.SetMethod("popupAt", &Menu::PopupAt)
|
||||
.SetMethod("closePopupAt", &Menu::ClosePopupAt)
|
||||
#if DCHECK_IS_ON()
|
||||
.SetMethod("getAcceleratorTextAt", &Menu::GetAcceleratorTextAtForTesting)
|
||||
#endif
|
||||
.Build();
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,9 @@ class Menu : public gin::Wrappable<Menu>,
|
||||
int positioning_item,
|
||||
base::OnceClosure callback) = 0;
|
||||
virtual void ClosePopupAt(int32_t window_id) = 0;
|
||||
#if DCHECK_IS_ON()
|
||||
virtual std::u16string GetAcceleratorTextAtForTesting(int index) const;
|
||||
#endif
|
||||
|
||||
std::unique_ptr<ElectronMenuModel> model_;
|
||||
Menu* parent_ = nullptr;
|
||||
@@ -111,7 +114,6 @@ class Menu : public gin::Wrappable<Menu>,
|
||||
std::u16string GetLabelAt(int index) const;
|
||||
std::u16string GetSublabelAt(int index) const;
|
||||
std::u16string GetToolTipAt(int index) const;
|
||||
std::u16string GetAcceleratorTextAt(int index) const;
|
||||
bool IsItemCheckedAt(int index) const;
|
||||
bool IsEnabledAt(int index) const;
|
||||
bool IsVisibleAt(int index) const;
|
||||
|
||||
@@ -35,11 +35,14 @@ class MenuMac : public Menu {
|
||||
int positioning_item,
|
||||
base::OnceClosure callback);
|
||||
void ClosePopupAt(int32_t window_id) override;
|
||||
void ClosePopupOnUI(int32_t window_id);
|
||||
#if DCHECK_IS_ON()
|
||||
std::u16string GetAcceleratorTextAtForTesting(int index) const override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
friend class Menu;
|
||||
|
||||
void ClosePopupOnUI(int32_t window_id);
|
||||
void OnClosed(int32_t window_id, base::OnceClosure callback);
|
||||
|
||||
scoped_nsobject<ElectronMenuController> menu_controller_;
|
||||
|
||||
@@ -127,6 +127,44 @@ void MenuMac::ClosePopupAt(int32_t window_id) {
|
||||
std::move(close_popup));
|
||||
}
|
||||
|
||||
#if DCHECK_IS_ON()
|
||||
std::u16string MenuMac::GetAcceleratorTextAtForTesting(int index) const {
|
||||
// A least effort to get the real shortcut text of NSMenuItem, the code does
|
||||
// not need to be perfect since it is test only.
|
||||
base::scoped_nsobject<ElectronMenuController> controller(
|
||||
[[ElectronMenuController alloc] initWithModel:model()
|
||||
useDefaultAccelerator:NO]);
|
||||
NSMenuItem* item = [[controller menu] itemAtIndex:index];
|
||||
std::u16string text;
|
||||
NSEventModifierFlags modifiers = [item keyEquivalentModifierMask];
|
||||
if (modifiers & NSEventModifierFlagControl)
|
||||
text += u"Ctrl";
|
||||
if (modifiers & NSEventModifierFlagShift) {
|
||||
if (!text.empty())
|
||||
text += u"+";
|
||||
text += u"Shift";
|
||||
}
|
||||
if (modifiers & NSEventModifierFlagOption) {
|
||||
if (!text.empty())
|
||||
text += u"+";
|
||||
text += u"Alt";
|
||||
}
|
||||
if (modifiers & NSEventModifierFlagCommand) {
|
||||
if (!text.empty())
|
||||
text += u"+";
|
||||
text += u"Command";
|
||||
}
|
||||
if (!text.empty())
|
||||
text += u"+";
|
||||
auto key = base::ToUpperASCII(base::SysNSStringToUTF16([item keyEquivalent]));
|
||||
if (key == u"\t")
|
||||
text += u"Tab";
|
||||
else
|
||||
text += key;
|
||||
return text;
|
||||
}
|
||||
#endif
|
||||
|
||||
void MenuMac::ClosePopupOnUI(int32_t window_id) {
|
||||
auto controller = popup_controllers_.find(window_id);
|
||||
if (controller != popup_controllers_.end()) {
|
||||
|
||||
@@ -3469,6 +3469,30 @@ void WebContents::DevToolsSearchInPath(int request_id,
|
||||
file_system_path));
|
||||
}
|
||||
|
||||
void WebContents::DevToolsSetEyeDropperActive(bool active) {
|
||||
auto* web_contents = GetWebContents();
|
||||
if (!web_contents)
|
||||
return;
|
||||
|
||||
if (active) {
|
||||
eye_dropper_ = std::make_unique<DevToolsEyeDropper>(
|
||||
web_contents, base::BindRepeating(&WebContents::ColorPickedInEyeDropper,
|
||||
base::Unretained(this)));
|
||||
} else {
|
||||
eye_dropper_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void WebContents::ColorPickedInEyeDropper(int r, int g, int b, int a) {
|
||||
base::DictionaryValue color;
|
||||
color.SetInteger("r", r);
|
||||
color.SetInteger("g", g);
|
||||
color.SetInteger("b", b);
|
||||
color.SetInteger("a", a);
|
||||
inspectable_web_contents_->CallClientFunction(
|
||||
"DevToolsAPI.eyeDropperPickedColor", &color, nullptr, nullptr);
|
||||
}
|
||||
|
||||
#if defined(TOOLKIT_VIEWS) && !defined(OS_MAC)
|
||||
gfx::ImageSkia WebContents::GetDevToolsWindowIcon() {
|
||||
if (!owner_window())
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/observer_list.h"
|
||||
#include "base/observer_list_types.h"
|
||||
#include "chrome/browser/devtools/devtools_eye_dropper.h"
|
||||
#include "chrome/browser/devtools/devtools_file_system_indexer.h"
|
||||
#include "content/common/cursors/webcursor.h"
|
||||
#include "content/common/frame.mojom.h"
|
||||
@@ -663,6 +664,7 @@ class WebContents : public gin::Wrappable<WebContents>,
|
||||
void DevToolsSearchInPath(int request_id,
|
||||
const std::string& file_system_path,
|
||||
const std::string& query) override;
|
||||
void DevToolsSetEyeDropperActive(bool active) override;
|
||||
|
||||
// InspectableWebContentsViewDelegate:
|
||||
#if defined(TOOLKIT_VIEWS) && !defined(OS_MAC)
|
||||
@@ -676,6 +678,8 @@ class WebContents : public gin::Wrappable<WebContents>,
|
||||
// Destroy the managed InspectableWebContents object.
|
||||
void ResetManagedWebContents(bool async);
|
||||
|
||||
void ColorPickedInEyeDropper(int r, int g, int b, int a);
|
||||
|
||||
// DevTools index event callbacks.
|
||||
void OnDevToolsIndexingWorkCalculated(int request_id,
|
||||
const std::string& file_system_path,
|
||||
@@ -747,6 +751,8 @@ class WebContents : public gin::Wrappable<WebContents>,
|
||||
|
||||
scoped_refptr<DevToolsFileSystemIndexer> devtools_file_system_indexer_;
|
||||
|
||||
std::unique_ptr<DevToolsEyeDropper> eye_dropper_;
|
||||
|
||||
ElectronBrowserContext* browser_context_;
|
||||
|
||||
// The stored InspectableWebContents object.
|
||||
|
||||
@@ -129,7 +129,7 @@ v8::Local<v8::Value> HttpResponseHeadersToV8(
|
||||
!value.empty()) {
|
||||
net::HttpContentDisposition header(value, std::string());
|
||||
std::string decodedFilename =
|
||||
header.is_attachment() ? " attachement" : " inline";
|
||||
header.is_attachment() ? " attachment" : " inline";
|
||||
decodedFilename += "; filename=" + header.filename();
|
||||
value = decodedFilename;
|
||||
}
|
||||
@@ -214,8 +214,11 @@ void ReadFromResponse(v8::Isolate* isolate,
|
||||
void ReadFromResponse(v8::Isolate* isolate,
|
||||
gin::Dictionary* response,
|
||||
net::HttpRequestHeaders* headers) {
|
||||
headers->Clear();
|
||||
response->Get("requestHeaders", headers);
|
||||
v8::Local<v8::Value> value;
|
||||
if (response->Get("requestHeaders", &value) && value->IsObject()) {
|
||||
headers->Clear();
|
||||
gin::Converter<net::HttpRequestHeaders>::FromV8(isolate, value, headers);
|
||||
}
|
||||
}
|
||||
|
||||
void ReadFromResponse(v8::Isolate* isolate,
|
||||
|
||||
@@ -1107,7 +1107,13 @@ ElectronBrowserClient::GetSystemNetworkContext() {
|
||||
std::unique_ptr<content::BrowserMainParts>
|
||||
ElectronBrowserClient::CreateBrowserMainParts(
|
||||
const content::MainFunctionParams& params) {
|
||||
return std::make_unique<ElectronBrowserMainParts>(params);
|
||||
auto browser_main_parts = std::make_unique<ElectronBrowserMainParts>(params);
|
||||
|
||||
#if defined(OS_MAC)
|
||||
browser_main_parts_ = browser_main_parts.get();
|
||||
#endif
|
||||
|
||||
return browser_main_parts;
|
||||
}
|
||||
|
||||
void ElectronBrowserClient::WebNotificationAllowed(
|
||||
@@ -1817,4 +1823,13 @@ content::BluetoothDelegate* ElectronBrowserClient::GetBluetoothDelegate() {
|
||||
return bluetooth_delegate_.get();
|
||||
}
|
||||
|
||||
device::GeolocationSystemPermissionManager*
|
||||
ElectronBrowserClient::GetLocationPermissionManager() {
|
||||
#if defined(OS_MAC)
|
||||
return browser_main_parts_->GetLocationPermissionManager();
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -33,6 +33,7 @@ class SSLCertRequestInfo;
|
||||
|
||||
namespace electron {
|
||||
|
||||
class ElectronBrowserMainParts;
|
||||
class NotificationPresenter;
|
||||
class PlatformNotificationService;
|
||||
|
||||
@@ -89,6 +90,9 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
|
||||
|
||||
content::BluetoothDelegate* GetBluetoothDelegate() override;
|
||||
|
||||
device::GeolocationSystemPermissionManager* GetLocationPermissionManager()
|
||||
override;
|
||||
|
||||
protected:
|
||||
void RenderProcessWillLaunch(content::RenderProcessHost* host) override;
|
||||
content::SpeechRecognitionManagerDelegate*
|
||||
@@ -342,6 +346,10 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
|
||||
std::unique_ptr<ElectronSerialDelegate> serial_delegate_;
|
||||
std::unique_ptr<ElectronBluetoothDelegate> bluetooth_delegate_;
|
||||
|
||||
#if defined(OS_MAC)
|
||||
ElectronBrowserMainParts* browser_main_parts_ = nullptr;
|
||||
#endif
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ElectronBrowserClient);
|
||||
};
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
#include "shell/browser/ui/devtools_manager_delegate.h"
|
||||
#include "shell/common/api/electron_bindings.h"
|
||||
#include "shell/common/application_info.h"
|
||||
#include "shell/common/asar/asar_util.h"
|
||||
#include "shell/common/electron_paths.h"
|
||||
#include "shell/common/gin_helper/trackable_object.h"
|
||||
#include "shell/common/node_bindings.h"
|
||||
@@ -96,6 +95,7 @@
|
||||
#endif
|
||||
|
||||
#if defined(OS_MAC)
|
||||
#include "services/device/public/cpp/geolocation/geolocation_system_permission_mac.h"
|
||||
#include "shell/browser/ui/cocoa/views_delegate_mac.h"
|
||||
#else
|
||||
#include "shell/browser/ui/views/electron_views_delegate.h"
|
||||
@@ -211,9 +211,7 @@ ElectronBrowserMainParts::ElectronBrowserMainParts(
|
||||
self_ = this;
|
||||
}
|
||||
|
||||
ElectronBrowserMainParts::~ElectronBrowserMainParts() {
|
||||
asar::ClearArchives();
|
||||
}
|
||||
ElectronBrowserMainParts::~ElectronBrowserMainParts() = default;
|
||||
|
||||
// static
|
||||
ElectronBrowserMainParts* ElectronBrowserMainParts::Get() {
|
||||
@@ -572,6 +570,13 @@ ElectronBrowserMainParts::GetGeolocationControl() {
|
||||
return geolocation_control_.get();
|
||||
}
|
||||
|
||||
#if defined(OS_MAC)
|
||||
device::GeolocationSystemPermissionManager*
|
||||
ElectronBrowserMainParts::GetLocationPermissionManager() {
|
||||
return location_permission_manager_.get();
|
||||
}
|
||||
#endif
|
||||
|
||||
IconManager* ElectronBrowserMainParts::GetIconManager() {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
if (!icon_manager_.get())
|
||||
|
||||
@@ -36,6 +36,10 @@ class GtkUiDelegate;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace device {
|
||||
class GeolocationSystemPermissionManager;
|
||||
} // namespace device
|
||||
|
||||
namespace electron {
|
||||
|
||||
class ElectronBrowserContext;
|
||||
@@ -80,6 +84,10 @@ class ElectronBrowserMainParts : public content::BrowserMainParts {
|
||||
// used to enable the location services once per client.
|
||||
device::mojom::GeolocationControl* GetGeolocationControl();
|
||||
|
||||
#if defined(OS_MAC)
|
||||
device::GeolocationSystemPermissionManager* GetLocationPermissionManager();
|
||||
#endif
|
||||
|
||||
// Returns handle to the class responsible for extracting file icons.
|
||||
IconManager* GetIconManager();
|
||||
|
||||
@@ -161,6 +169,11 @@ class ElectronBrowserMainParts : public content::BrowserMainParts {
|
||||
|
||||
mojo::Remote<device::mojom::GeolocationControl> geolocation_control_;
|
||||
|
||||
#if defined(OS_MAC)
|
||||
std::unique_ptr<device::GeolocationSystemPermissionManager>
|
||||
location_permission_manager_;
|
||||
#endif
|
||||
|
||||
static ElectronBrowserMainParts* self_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ElectronBrowserMainParts);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "base/mac/bundle_locations.h"
|
||||
#include "base/mac/foundation_util.h"
|
||||
#include "base/path_service.h"
|
||||
#include "services/device/public/cpp/geolocation/geolocation_system_permission_mac.h"
|
||||
#import "shell/browser/mac/electron_application.h"
|
||||
#include "shell/browser/mac/electron_application_delegate.h"
|
||||
#include "shell/common/electron_paths.h"
|
||||
@@ -27,6 +28,9 @@ void ElectronBrowserMainParts::PreMainMessageLoopStart() {
|
||||
[[NSUserDefaults standardUserDefaults]
|
||||
setObject:@"NO"
|
||||
forKey:@"NSTreatUnknownArgumentsAsOpen"];
|
||||
|
||||
location_permission_manager_ =
|
||||
device::GeolocationSystemPermissionManager::Create();
|
||||
}
|
||||
|
||||
void ElectronBrowserMainParts::FreeAppDelegate() {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "base/command_line.h"
|
||||
#include "base/feature_list.h"
|
||||
#include "base/metrics/field_trial.h"
|
||||
#include "components/spellcheck/common/spellcheck_features.h"
|
||||
#include "content/public/common/content_features.h"
|
||||
#include "electron/buildflags/buildflags.h"
|
||||
#include "media/base/media_switches.h"
|
||||
@@ -41,6 +42,13 @@ void InitializeFeatureList() {
|
||||
#if !BUILDFLAG(ENABLE_PICTURE_IN_PICTURE)
|
||||
disable_features += std::string(",") + media::kPictureInPicture.name;
|
||||
#endif
|
||||
|
||||
#if defined(OS_WIN)
|
||||
// Disable async spellchecker suggestions for Windows, which causes
|
||||
// an empty suggestions list to be returned
|
||||
disable_features +=
|
||||
std::string(",") + spellcheck::kWinRetrieveSuggestionsOnlyOnDemand.name;
|
||||
#endif
|
||||
base::FeatureList::InitializeInstance(enable_features, disable_features);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ void BluetoothChooser::SetAdapterPresence(AdapterPresence presence) {
|
||||
event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, "");
|
||||
break;
|
||||
case AdapterPresence::POWERED_ON:
|
||||
rescan_ = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -92,7 +93,7 @@ void BluetoothChooser::ShowDiscoveryState(DiscoveryState state) {
|
||||
case DiscoveryState::DISCOVERING:
|
||||
// The first time this state fires is due to a rescan triggering so set a
|
||||
// flag to ignore devices
|
||||
if (!refreshing_) {
|
||||
if (rescan_ && !refreshing_) {
|
||||
refreshing_ = true;
|
||||
} else {
|
||||
// The second time this state fires we are now safe to pick a device
|
||||
|
||||
@@ -42,6 +42,7 @@ class BluetoothChooser : public content::BluetoothChooser {
|
||||
EventHandler event_handler_;
|
||||
int num_retries_ = 0;
|
||||
bool refreshing_ = false;
|
||||
bool rescan_ = false;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BluetoothChooser);
|
||||
};
|
||||
|
||||
@@ -201,6 +201,7 @@ class NativeWindow : public base::SupportsUserData,
|
||||
|
||||
// Traffic Light API
|
||||
#if defined(OS_MAC)
|
||||
virtual std::string GetAlwaysOnTopLevel() = 0;
|
||||
virtual void SetWindowButtonVisibility(bool visible) = 0;
|
||||
virtual bool GetWindowButtonVisibility() const = 0;
|
||||
virtual void SetTrafficLightPosition(base::Optional<gfx::Point> position) = 0;
|
||||
|
||||
@@ -79,6 +79,7 @@ class NativeWindowMac : public NativeWindow,
|
||||
void SetAlwaysOnTop(ui::ZOrderLevel z_order,
|
||||
const std::string& level,
|
||||
int relative_level) override;
|
||||
std::string GetAlwaysOnTopLevel() override;
|
||||
ui::ZOrderLevel GetZOrderLevel() override;
|
||||
void Center() override;
|
||||
void Invalidate() override;
|
||||
@@ -152,6 +153,9 @@ class NativeWindowMac : public NativeWindow,
|
||||
void NotifyWindowWillEnterFullScreen();
|
||||
void NotifyWindowWillLeaveFullScreen();
|
||||
|
||||
// Ensure the buttons view are always floated on the top.
|
||||
void ReorderButtonsView();
|
||||
|
||||
// Cleanup observers when window is getting closed. Note that the destructor
|
||||
// can be called much later after window gets closed, so we should not do
|
||||
// cleanup in destructor.
|
||||
|
||||
@@ -458,11 +458,8 @@ void NativeWindowMac::SetContentView(views::View* view) {
|
||||
set_content_view(view);
|
||||
root_view->AddChildView(content_view());
|
||||
|
||||
if (buttons_view_) {
|
||||
// Ensure the buttons view are always floated on the top.
|
||||
[buttons_view_ removeFromSuperview];
|
||||
[[window_ contentView] addSubview:buttons_view_];
|
||||
}
|
||||
if (buttons_view_)
|
||||
ReorderButtonsView();
|
||||
|
||||
root_view->Layout();
|
||||
}
|
||||
@@ -554,6 +551,15 @@ void NativeWindowMac::Hide() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide all children of the current window before hiding the window.
|
||||
// components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
|
||||
// expects this when window visibility changes.
|
||||
if ([window_ childWindows]) {
|
||||
for (NSWindow* child in [window_ childWindows]) {
|
||||
[child orderOut:nil];
|
||||
}
|
||||
}
|
||||
|
||||
// Deattach the window from the parent before.
|
||||
if (parent())
|
||||
InternalSetParentWindow(parent(), false);
|
||||
@@ -869,6 +875,31 @@ void NativeWindowMac::SetAlwaysOnTop(ui::ZOrderLevel z_order,
|
||||
SetWindowLevel(level + relative_level);
|
||||
}
|
||||
|
||||
std::string NativeWindowMac::GetAlwaysOnTopLevel() {
|
||||
std::string level_name = "normal";
|
||||
|
||||
int level = [window_ level];
|
||||
if (level == NSFloatingWindowLevel) {
|
||||
level_name = "floating";
|
||||
} else if (level == NSTornOffMenuWindowLevel) {
|
||||
level_name = "torn-off-menu";
|
||||
} else if (level == NSModalPanelWindowLevel) {
|
||||
level_name = "modal-panel";
|
||||
} else if (level == NSMainMenuWindowLevel) {
|
||||
level_name = "main-menu";
|
||||
} else if (level == NSStatusWindowLevel) {
|
||||
level_name = "status";
|
||||
} else if (level == NSPopUpMenuWindowLevel) {
|
||||
level_name = "pop-up-menu";
|
||||
} else if (level == NSScreenSaverWindowLevel) {
|
||||
level_name = "screen-saver";
|
||||
} else if (level == NSDockWindowLevel) {
|
||||
level_name = "dock";
|
||||
}
|
||||
|
||||
return level_name;
|
||||
}
|
||||
|
||||
void NativeWindowMac::SetWindowLevel(int unbounded_level) {
|
||||
int level = std::min(
|
||||
std::max(unbounded_level, CGWindowLevelForKey(kCGMinimumWindowLevelKey)),
|
||||
@@ -1143,6 +1174,8 @@ void NativeWindowMac::AddBrowserView(NativeBrowserView* view) {
|
||||
}
|
||||
|
||||
[CATransaction commit];
|
||||
|
||||
ReorderButtonsView();
|
||||
}
|
||||
|
||||
void NativeWindowMac::RemoveBrowserView(NativeBrowserView* view) {
|
||||
@@ -1184,6 +1217,8 @@ void NativeWindowMac::SetTopBrowserView(NativeBrowserView* view) {
|
||||
}
|
||||
|
||||
[CATransaction commit];
|
||||
|
||||
ReorderButtonsView();
|
||||
}
|
||||
|
||||
void NativeWindowMac::SetParentWindow(NativeWindow* parent) {
|
||||
@@ -1352,8 +1387,6 @@ void NativeWindowMac::SetVibrancy(const std::string& type) {
|
||||
return;
|
||||
}
|
||||
|
||||
vibrancy_type_ = type;
|
||||
|
||||
NSVisualEffectView* effect_view = (NSVisualEffectView*)vibrant_view;
|
||||
if (effect_view == nil) {
|
||||
effect_view = [[[NSVisualEffectView alloc]
|
||||
@@ -1382,7 +1415,7 @@ void NativeWindowMac::SetVibrancy(const std::string& type) {
|
||||
node::Environment* env =
|
||||
node::Environment::GetCurrent(JavascriptEnvironment::GetIsolate());
|
||||
|
||||
NSVisualEffectMaterial vibrancyType;
|
||||
NSVisualEffectMaterial vibrancyType{};
|
||||
if (type == "appearance-based") {
|
||||
EmitWarning(env, "NSVisualEffectMaterialAppearanceBased" + dep_warn,
|
||||
"electron");
|
||||
@@ -1439,8 +1472,10 @@ void NativeWindowMac::SetVibrancy(const std::string& type) {
|
||||
}
|
||||
}
|
||||
|
||||
if (vibrancyType)
|
||||
if (vibrancyType) {
|
||||
vibrancy_type_ = type;
|
||||
[effect_view setMaterial:vibrancyType];
|
||||
}
|
||||
}
|
||||
|
||||
void NativeWindowMac::SetWindowButtonVisibility(bool visible) {
|
||||
@@ -1636,6 +1671,13 @@ void NativeWindowMac::NotifyWindowWillLeaveFullScreen() {
|
||||
UpdateVibrancyRadii(false);
|
||||
}
|
||||
|
||||
void NativeWindowMac::ReorderButtonsView() {
|
||||
if (buttons_view_) {
|
||||
[buttons_view_ removeFromSuperview];
|
||||
[[window_ contentView] addSubview:buttons_view_];
|
||||
}
|
||||
}
|
||||
|
||||
void NativeWindowMac::Cleanup() {
|
||||
DCHECK(!IsClosed());
|
||||
ui::NativeTheme::GetInstanceForNativeUi()->RemoveObserver(this);
|
||||
@@ -1786,10 +1828,15 @@ void NativeWindowMac::InternalSetParentWindow(NativeWindow* parent,
|
||||
|
||||
// Set new parent window.
|
||||
// Note that this method will force the window to become visible.
|
||||
if (parent && attach)
|
||||
if (parent && attach) {
|
||||
// Attaching a window as a child window resets its window level, so
|
||||
// save and restore it afterwards.
|
||||
NSInteger level = window_.level;
|
||||
[parent->GetNativeWindow().GetNativeNSWindow()
|
||||
addChildWindow:window_
|
||||
ordered:NSWindowAbove];
|
||||
[window_ setLevel:level];
|
||||
}
|
||||
}
|
||||
|
||||
void NativeWindowMac::SetForwardMouseMessages(bool forward) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "extensions/browser/extension_navigation_ui_data.h"
|
||||
#include "net/base/completion_repeating_callback.h"
|
||||
#include "net/base/load_flags.h"
|
||||
#include "net/http/http_status_code.h"
|
||||
#include "net/http/http_util.h"
|
||||
#include "services/metrics/public/cpp/ukm_source_id.h"
|
||||
#include "services/network/public/cpp/features.h"
|
||||
@@ -313,6 +314,14 @@ void ProxyingURLLoaderFactory::InProgressRequest::OnComplete(
|
||||
|
||||
void ProxyingURLLoaderFactory::InProgressRequest::OnLoaderCreated(
|
||||
mojo::PendingReceiver<network::mojom::TrustedHeaderClient> receiver) {
|
||||
// When CORS is involved there may be multiple network::URLLoader associated
|
||||
// with this InProgressRequest, because CorsURLLoader may create a new
|
||||
// network::URLLoader for the same request id in redirect handling - see
|
||||
// CorsURLLoader::FollowRedirect. In such a case the old network::URLLoader
|
||||
// is going to be detached fairly soon, so we don't need to take care of it.
|
||||
// We need this explicit reset to avoid a DCHECK failure in mojo::Receiver.
|
||||
header_client_receiver_.reset();
|
||||
|
||||
header_client_receiver_.Bind(std::move(receiver));
|
||||
if (for_cors_preflight_) {
|
||||
// In this case we don't have |target_loader_| and
|
||||
@@ -555,13 +564,17 @@ void ProxyingURLLoaderFactory::InProgressRequest::
|
||||
override_headers_ = nullptr;
|
||||
|
||||
if (for_cors_preflight_) {
|
||||
// If this is for CORS preflight, there is no associated client. We notify
|
||||
// the completion here, and deletes |this|.
|
||||
// If this is for CORS preflight, there is no associated client.
|
||||
info_->AddResponseInfoFromResourceResponse(*current_response_);
|
||||
// Do not finish proxied preflight requests that require proxy auth.
|
||||
// The request is not finished yet, give control back to network service
|
||||
// which will start authentication process.
|
||||
if (info_->response_code == net::HTTP_PROXY_AUTHENTICATION_REQUIRED)
|
||||
return;
|
||||
// We notify the completion here, and delete |this|.
|
||||
factory_->web_request_api()->OnResponseStarted(&info_.value(), request_);
|
||||
factory_->web_request_api()->OnCompleted(&info_.value(), request_, net::OK);
|
||||
|
||||
// Deletes |this|.
|
||||
factory_->RemoveRequest(network_service_request_id_, request_id_);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ void URLPipeLoader::OnDataReceived(base::StringPiece string_piece,
|
||||
producer_->Write(
|
||||
std::make_unique<mojo::StringDataSource>(
|
||||
string_piece, mojo::StringDataSource::AsyncWritingMode::
|
||||
STRING_STAYS_VALID_UNTIL_COMPLETION),
|
||||
STRING_MAY_BE_INVALIDATED_BEFORE_COMPLETION),
|
||||
base::BindOnce(&URLPipeLoader::OnWrite, weak_factory_.GetWeakPtr(),
|
||||
std::move(resume)));
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ END
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 13,1,1,0
|
||||
PRODUCTVERSION 13,1,1,0
|
||||
FILEVERSION 13,1,5,0
|
||||
PRODUCTVERSION 13,1,5,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -68,12 +68,12 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "GitHub, Inc."
|
||||
VALUE "FileDescription", "Electron"
|
||||
VALUE "FileVersion", "13.1.1"
|
||||
VALUE "FileVersion", "13.1.5"
|
||||
VALUE "InternalName", "electron.exe"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved."
|
||||
VALUE "OriginalFilename", "electron.exe"
|
||||
VALUE "ProductName", "Electron"
|
||||
VALUE "ProductVersion", "13.1.1"
|
||||
VALUE "ProductVersion", "13.1.5"
|
||||
VALUE "SquirrelAwareVersion", "1"
|
||||
END
|
||||
END
|
||||
|
||||
@@ -31,10 +31,10 @@ bool StringToAccelerator(const std::string& shortcut,
|
||||
// Now, parse it into an accelerator.
|
||||
int modifiers = ui::EF_NONE;
|
||||
ui::KeyboardCode key = ui::VKEY_UNKNOWN;
|
||||
base::Optional<char16_t> shifted_char;
|
||||
for (const auto& token : tokens) {
|
||||
bool shifted = false;
|
||||
ui::KeyboardCode code = electron::KeyboardCodeFromStr(token, &shifted);
|
||||
if (shifted)
|
||||
ui::KeyboardCode code = electron::KeyboardCodeFromStr(token, &shifted_char);
|
||||
if (shifted_char)
|
||||
modifiers |= ui::EF_SHIFT_DOWN;
|
||||
switch (code) {
|
||||
// The token can be a modifier.
|
||||
@@ -65,6 +65,7 @@ bool StringToAccelerator(const std::string& shortcut,
|
||||
}
|
||||
|
||||
*accelerator = ui::Accelerator(key, modifiers);
|
||||
accelerator->shifted_char = shifted_char;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -160,13 +160,12 @@
|
||||
|
||||
// Switch to new state.
|
||||
devtools_docked_ = docked;
|
||||
auto* inspectable_web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents();
|
||||
auto* devToolsWebContents =
|
||||
inspectable_web_contents->GetDevToolsWebContents();
|
||||
auto devToolsView = devToolsWebContents->GetNativeView().GetNativeNSView();
|
||||
if (!docked) {
|
||||
auto* inspectable_web_contents =
|
||||
inspectableWebContentsView_->inspectable_web_contents();
|
||||
auto* devToolsWebContents =
|
||||
inspectable_web_contents->GetDevToolsWebContents();
|
||||
auto devToolsView = devToolsWebContents->GetNativeView().GetNativeNSView();
|
||||
|
||||
auto styleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
|
||||
NSMiniaturizableWindowMask | NSWindowStyleMaskResizable |
|
||||
NSTexturedBackgroundWindowMask |
|
||||
@@ -189,6 +188,9 @@
|
||||
devToolsView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
|
||||
|
||||
[contentView addSubview:devToolsView];
|
||||
[devToolsView setMouseDownCanMoveWindow:NO];
|
||||
} else {
|
||||
[devToolsView setMouseDownCanMoveWindow:YES];
|
||||
}
|
||||
[self setDevToolsVisible:YES activate:activate];
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
#include "shell/browser/ui/electron_menu_model.h"
|
||||
#include "shell/browser/window_list.h"
|
||||
#include "ui/base/accelerators/accelerator.h"
|
||||
#include "ui/base/accelerators/platform_accelerator_cocoa.h"
|
||||
#include "ui/base/l10n/l10n_util_mac.h"
|
||||
#include "ui/events/cocoa/cocoa_event_utils.h"
|
||||
#include "ui/events/keycodes/keyboard_code_conversion_mac.h"
|
||||
#include "ui/gfx/image/image.h"
|
||||
#include "ui/strings/grit/ui_strings.h"
|
||||
|
||||
@@ -394,11 +394,31 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
|
||||
ui::Accelerator accelerator;
|
||||
if (model->GetAcceleratorAtWithParams(index, useDefaultAccelerator_,
|
||||
&accelerator)) {
|
||||
NSString* key_equivalent;
|
||||
NSUInteger modifier_mask;
|
||||
GetKeyEquivalentAndModifierMaskFromAccelerator(
|
||||
accelerator, &key_equivalent, &modifier_mask);
|
||||
[item setKeyEquivalent:key_equivalent];
|
||||
// Note that we are not using Chromium's
|
||||
// GetKeyEquivalentAndModifierMaskFromAccelerator API,
|
||||
// because it will convert Shift+Character to ShiftedCharacter, for
|
||||
// example Shift+/ would be converted to ?, which is against macOS HIG.
|
||||
// See also https://github.com/electron/electron/issues/21790.
|
||||
NSUInteger modifier_mask = 0;
|
||||
if (accelerator.IsCtrlDown())
|
||||
modifier_mask |= NSEventModifierFlagControl;
|
||||
if (accelerator.IsAltDown())
|
||||
modifier_mask |= NSEventModifierFlagOption;
|
||||
if (accelerator.IsCmdDown())
|
||||
modifier_mask |= NSEventModifierFlagCommand;
|
||||
unichar character;
|
||||
if (accelerator.shifted_char) {
|
||||
// When a shifted char is explicitly specified, for example Ctrl+Plus,
|
||||
// use the shifted char directly.
|
||||
character = static_cast<unichar>(*accelerator.shifted_char);
|
||||
} else {
|
||||
// Otherwise use the unshifted combinations, for example Ctrl+Shift+=.
|
||||
if (accelerator.IsShiftDown())
|
||||
modifier_mask |= NSEventModifierFlagShift;
|
||||
ui::MacKeyCodeForWindowsKeyCode(accelerator.key_code(), modifier_mask,
|
||||
nullptr, &character);
|
||||
}
|
||||
[item setKeyEquivalent:[NSString stringWithFormat:@"%C", character]];
|
||||
[item setKeyEquivalentModifierMask:modifier_mask];
|
||||
}
|
||||
|
||||
|
||||
@@ -784,7 +784,10 @@ void InspectableWebContents::SearchInPath(int request_id,
|
||||
void InspectableWebContents::SetWhitelistedShortcuts(
|
||||
const std::string& message) {}
|
||||
|
||||
void InspectableWebContents::SetEyeDropperActive(bool active) {}
|
||||
void InspectableWebContents::SetEyeDropperActive(bool active) {
|
||||
if (delegate_)
|
||||
delegate_->DevToolsSetEyeDropperActive(active);
|
||||
}
|
||||
void InspectableWebContents::ShowCertificateViewer(
|
||||
const std::string& cert_chain) {}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ class InspectableWebContentsDelegate {
|
||||
virtual void DevToolsSearchInPath(int request_id,
|
||||
const std::string& file_system_path,
|
||||
const std::string& query) {}
|
||||
virtual void DevToolsSetEyeDropperActive(bool active) {}
|
||||
};
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -38,6 +38,13 @@ int FramelessView::ResizingBorderHitTest(const gfx::Point& point) {
|
||||
bool can_ever_resize = frame_->widget_delegate()
|
||||
? frame_->widget_delegate()->CanResize()
|
||||
: false;
|
||||
|
||||
// https://github.com/electron/electron/issues/611
|
||||
// If window isn't resizable, we should always return HTCLIENT, otherwise the
|
||||
// hover state of DOM will not be cleared probably.
|
||||
if (!can_ever_resize)
|
||||
return HTCLIENT;
|
||||
|
||||
// Don't allow overlapping resize handles when the window is maximized or
|
||||
// fullscreen, as it can't be resized in those states.
|
||||
int resize_border = frame_->IsMaximized() || frame_->IsFullscreen()
|
||||
|
||||
@@ -128,6 +128,10 @@ void InspectableWebContentsViewViews::ShowDevTools(bool activate) {
|
||||
} else {
|
||||
devtools_window_->ShowInactive();
|
||||
}
|
||||
|
||||
// Update draggable regions to account for the new dock position.
|
||||
if (GetDelegate())
|
||||
GetDelegate()->DevToolsResized();
|
||||
} else {
|
||||
devtools_web_view_->SetVisible(true);
|
||||
devtools_web_view_->SetWebContents(
|
||||
@@ -228,6 +232,9 @@ void InspectableWebContentsViewViews::Layout() {
|
||||
|
||||
devtools_web_view_->SetBoundsRect(new_devtools_bounds);
|
||||
contents_web_view_->SetBoundsRect(new_contents_bounds);
|
||||
|
||||
if (GetDelegate())
|
||||
GetDelegate()->DevToolsResized();
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -107,7 +107,7 @@ bool IsSameOrigin(const GURL& l, const GURL& r) {
|
||||
return url::Origin::Create(l).IsSameOriginWith(url::Origin::Create(r));
|
||||
}
|
||||
|
||||
#ifdef DCHECK_IS_ON
|
||||
#if DCHECK_IS_ON()
|
||||
std::vector<v8::Global<v8::Value>> weakly_tracked_values;
|
||||
|
||||
void WeaklyTrackValue(v8::Isolate* isolate, v8::Local<v8::Value> value) {
|
||||
@@ -157,7 +157,7 @@ void Initialize(v8::Local<v8::Object> exports,
|
||||
dict.SetMethod("requestGarbageCollectionForTesting",
|
||||
&RequestGarbageCollectionForTesting);
|
||||
dict.SetMethod("isSameOrigin", &IsSameOrigin);
|
||||
#ifdef DCHECK_IS_ON
|
||||
#if DCHECK_IS_ON()
|
||||
dict.SetMethod("triggerFatalErrorForTesting", &TriggerFatalErrorForTesting);
|
||||
dict.SetMethod("getWeaklyTrackedValues", &GetWeaklyTrackedValues);
|
||||
dict.SetMethod("clearWeaklyTrackedValues", &ClearWeaklyTrackedValues);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/check.h"
|
||||
#include "base/files/file.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/json/json_reader.h"
|
||||
@@ -118,7 +119,7 @@ bool FillFileInfoWithNode(Archive::FileInfo* info,
|
||||
} // namespace
|
||||
|
||||
Archive::Archive(const base::FilePath& path)
|
||||
: path_(path), file_(base::File::FILE_OK) {
|
||||
: initialized_(false), path_(path), file_(base::File::FILE_OK) {
|
||||
base::ThreadRestrictions::ScopedAllowIO allow_io;
|
||||
file_.Initialize(path_, base::File::FLAG_OPEN | base::File::FLAG_READ);
|
||||
#if defined(OS_WIN)
|
||||
@@ -141,6 +142,10 @@ Archive::~Archive() {
|
||||
}
|
||||
|
||||
bool Archive::Init() {
|
||||
// Should only be initialized once
|
||||
CHECK(!initialized_);
|
||||
initialized_ = true;
|
||||
|
||||
if (!file_.IsValid()) {
|
||||
if (file_.error_details() != base::File::FILE_ERROR_NOT_FOUND) {
|
||||
LOG(WARNING) << "Opening " << path_.value() << ": "
|
||||
@@ -198,7 +203,7 @@ bool Archive::Init() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Archive::GetFileInfo(const base::FilePath& path, FileInfo* info) {
|
||||
bool Archive::GetFileInfo(const base::FilePath& path, FileInfo* info) const {
|
||||
if (!header_)
|
||||
return false;
|
||||
|
||||
@@ -213,7 +218,7 @@ bool Archive::GetFileInfo(const base::FilePath& path, FileInfo* info) {
|
||||
return FillFileInfoWithNode(info, header_size_, node);
|
||||
}
|
||||
|
||||
bool Archive::Stat(const base::FilePath& path, Stats* stats) {
|
||||
bool Archive::Stat(const base::FilePath& path, Stats* stats) const {
|
||||
if (!header_)
|
||||
return false;
|
||||
|
||||
@@ -237,7 +242,7 @@ bool Archive::Stat(const base::FilePath& path, Stats* stats) {
|
||||
}
|
||||
|
||||
bool Archive::Readdir(const base::FilePath& path,
|
||||
std::vector<base::FilePath>* files) {
|
||||
std::vector<base::FilePath>* files) const {
|
||||
if (!header_)
|
||||
return false;
|
||||
|
||||
@@ -257,7 +262,8 @@ bool Archive::Readdir(const base::FilePath& path,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Archive::Realpath(const base::FilePath& path, base::FilePath* realpath) {
|
||||
bool Archive::Realpath(const base::FilePath& path,
|
||||
base::FilePath* realpath) const {
|
||||
if (!header_)
|
||||
return false;
|
||||
|
||||
@@ -276,6 +282,11 @@ bool Archive::Realpath(const base::FilePath& path, base::FilePath* realpath) {
|
||||
}
|
||||
|
||||
bool Archive::CopyFileOut(const base::FilePath& path, base::FilePath* out) {
|
||||
if (!header_)
|
||||
return false;
|
||||
|
||||
base::AutoLock auto_lock(external_files_lock_);
|
||||
|
||||
auto it = external_files_.find(path.value());
|
||||
if (it != external_files_.end()) {
|
||||
*out = it->second->path();
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "base/files/file.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
|
||||
namespace base {
|
||||
class DictionaryValue;
|
||||
@@ -21,7 +22,7 @@ namespace asar {
|
||||
class ScopedTemporaryFile;
|
||||
|
||||
// This class represents an asar package, and provides methods to read
|
||||
// information from it.
|
||||
// information from it. It is thread-safe after |Init| has been called.
|
||||
class Archive {
|
||||
public:
|
||||
struct FileInfo {
|
||||
@@ -46,16 +47,17 @@ class Archive {
|
||||
bool Init();
|
||||
|
||||
// Get the info of a file.
|
||||
bool GetFileInfo(const base::FilePath& path, FileInfo* info);
|
||||
bool GetFileInfo(const base::FilePath& path, FileInfo* info) const;
|
||||
|
||||
// Fs.stat(path).
|
||||
bool Stat(const base::FilePath& path, Stats* stats);
|
||||
bool Stat(const base::FilePath& path, Stats* stats) const;
|
||||
|
||||
// Fs.readdir(path).
|
||||
bool Readdir(const base::FilePath& path, std::vector<base::FilePath>* files);
|
||||
bool Readdir(const base::FilePath& path,
|
||||
std::vector<base::FilePath>* files) const;
|
||||
|
||||
// Fs.realpath(path).
|
||||
bool Realpath(const base::FilePath& path, base::FilePath* realpath);
|
||||
bool Realpath(const base::FilePath& path, base::FilePath* realpath) const;
|
||||
|
||||
// Copy the file into a temporary file, and return the new path.
|
||||
// For unpacked file, this method will return its real path.
|
||||
@@ -65,16 +67,17 @@ class Archive {
|
||||
int GetFD() const;
|
||||
|
||||
base::FilePath path() const { return path_; }
|
||||
base::DictionaryValue* header() const { return header_.get(); }
|
||||
|
||||
private:
|
||||
base::FilePath path_;
|
||||
bool initialized_;
|
||||
const base::FilePath path_;
|
||||
base::File file_;
|
||||
int fd_ = -1;
|
||||
uint32_t header_size_ = 0;
|
||||
std::unique_ptr<base::DictionaryValue> header_;
|
||||
|
||||
// Cached external temporary files.
|
||||
base::Lock external_files_lock_;
|
||||
std::unordered_map<base::FilePath::StringType,
|
||||
std::unique_ptr<ScopedTemporaryFile>>
|
||||
external_files_;
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/lazy_instance.h"
|
||||
#include "base/no_destructor.h"
|
||||
#include "base/stl_util.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
#include "base/threading/thread_local.h"
|
||||
#include "base/threading/thread_restrictions.h"
|
||||
#include "shell/common/asar/archive.h"
|
||||
@@ -19,30 +22,41 @@ namespace asar {
|
||||
|
||||
namespace {
|
||||
|
||||
// The global instance of ArchiveMap, will be destroyed on exit.
|
||||
typedef std::map<base::FilePath, std::shared_ptr<Archive>> ArchiveMap;
|
||||
base::LazyInstance<base::ThreadLocalPointer<ArchiveMap>>::Leaky
|
||||
g_archive_map_tls = LAZY_INSTANCE_INITIALIZER;
|
||||
|
||||
const base::FilePath::CharType kAsarExtension[] = FILE_PATH_LITERAL(".asar");
|
||||
|
||||
std::map<base::FilePath, bool> g_is_directory_cache;
|
||||
|
||||
bool IsDirectoryCached(const base::FilePath& path) {
|
||||
auto it = g_is_directory_cache.find(path);
|
||||
if (it != g_is_directory_cache.end()) {
|
||||
static base::NoDestructor<std::map<base::FilePath, bool>>
|
||||
s_is_directory_cache;
|
||||
static base::NoDestructor<base::Lock> lock;
|
||||
|
||||
base::AutoLock auto_lock(*lock);
|
||||
auto& is_directory_cache = *s_is_directory_cache;
|
||||
|
||||
auto it = is_directory_cache.find(path);
|
||||
if (it != is_directory_cache.end()) {
|
||||
return it->second;
|
||||
}
|
||||
base::ThreadRestrictions::ScopedAllowIO allow_io;
|
||||
return g_is_directory_cache[path] = base::DirectoryExists(path);
|
||||
return is_directory_cache[path] = base::DirectoryExists(path);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ArchiveMap& GetArchiveCache() {
|
||||
static base::NoDestructor<ArchiveMap> s_archive_map;
|
||||
return *s_archive_map;
|
||||
}
|
||||
|
||||
base::Lock& GetArchiveCacheLock() {
|
||||
static base::NoDestructor<base::Lock> lock;
|
||||
return *lock;
|
||||
}
|
||||
|
||||
std::shared_ptr<Archive> GetOrCreateAsarArchive(const base::FilePath& path) {
|
||||
if (!g_archive_map_tls.Pointer()->Get())
|
||||
g_archive_map_tls.Pointer()->Set(new ArchiveMap);
|
||||
ArchiveMap& map = *g_archive_map_tls.Pointer()->Get();
|
||||
base::AutoLock auto_lock(GetArchiveCacheLock());
|
||||
ArchiveMap& map = GetArchiveCache();
|
||||
|
||||
// if we have it, return it
|
||||
const auto lower = map.lower_bound(path);
|
||||
@@ -61,8 +75,10 @@ std::shared_ptr<Archive> GetOrCreateAsarArchive(const base::FilePath& path) {
|
||||
}
|
||||
|
||||
void ClearArchives() {
|
||||
if (g_archive_map_tls.Pointer()->Get())
|
||||
delete g_archive_map_tls.Pointer()->Get();
|
||||
base::AutoLock auto_lock(GetArchiveCacheLock());
|
||||
ArchiveMap& map = GetArchiveCache();
|
||||
|
||||
map.clear();
|
||||
}
|
||||
|
||||
bool GetAsarArchivePath(const base::FilePath& full_path,
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace asar {
|
||||
|
||||
class Archive;
|
||||
|
||||
// Gets or creates a new Archive from the path.
|
||||
// Gets or creates and caches a new Archive from the path.
|
||||
std::shared_ptr<Archive> GetOrCreateAsarArchive(const base::FilePath& path);
|
||||
|
||||
// Destroy cached Archive objects.
|
||||
|
||||
@@ -187,10 +187,10 @@ bool Converter<blink::WebKeyboardEvent>::FromV8(v8::Isolate* isolate,
|
||||
if (!dict.Get("keyCode", &str))
|
||||
return false;
|
||||
|
||||
bool shifted = false;
|
||||
ui::KeyboardCode keyCode = electron::KeyboardCodeFromStr(str, &shifted);
|
||||
base::Optional<char16_t> shifted_char;
|
||||
ui::KeyboardCode keyCode = electron::KeyboardCodeFromStr(str, &shifted_char);
|
||||
out->windows_key_code = keyCode;
|
||||
if (shifted)
|
||||
if (shifted_char)
|
||||
out->SetModifiers(out->GetModifiers() |
|
||||
blink::WebInputEvent::Modifiers::kShiftKey);
|
||||
|
||||
|
||||
@@ -15,8 +15,9 @@ namespace electron {
|
||||
namespace {
|
||||
|
||||
// Return key code represented by |str|.
|
||||
ui::KeyboardCode KeyboardCodeFromKeyIdentifier(const std::string& s,
|
||||
bool* shifted) {
|
||||
ui::KeyboardCode KeyboardCodeFromKeyIdentifier(
|
||||
const std::string& s,
|
||||
base::Optional<char16_t>* shifted_char) {
|
||||
std::string str = base::ToLowerASCII(s);
|
||||
if (str == "ctrl" || str == "control") {
|
||||
return ui::VKEY_CONTROL;
|
||||
@@ -36,7 +37,7 @@ ui::KeyboardCode KeyboardCodeFromKeyIdentifier(const std::string& s,
|
||||
} else if (str == "altgr") {
|
||||
return ui::VKEY_ALTGR;
|
||||
} else if (str == "plus") {
|
||||
*shifted = true;
|
||||
shifted_char->emplace('+');
|
||||
return ui::VKEY_OEM_PLUS;
|
||||
} else if (str == "capslock") {
|
||||
return ui::VKEY_CAPITAL;
|
||||
@@ -319,11 +320,17 @@ ui::KeyboardCode KeyboardCodeFromCharCode(char16_t c, bool* shifted) {
|
||||
}
|
||||
}
|
||||
|
||||
ui::KeyboardCode KeyboardCodeFromStr(const std::string& str, bool* shifted) {
|
||||
if (str.size() == 1)
|
||||
return KeyboardCodeFromCharCode(str[0], shifted);
|
||||
else
|
||||
return KeyboardCodeFromKeyIdentifier(str, shifted);
|
||||
ui::KeyboardCode KeyboardCodeFromStr(const std::string& str,
|
||||
base::Optional<char16_t>* shifted_char) {
|
||||
if (str.size() == 1) {
|
||||
bool shifted = false;
|
||||
auto ret = KeyboardCodeFromCharCode(str[0], &shifted);
|
||||
if (shifted)
|
||||
shifted_char->emplace(str[0]);
|
||||
return ret;
|
||||
} else {
|
||||
return KeyboardCodeFromKeyIdentifier(str, shifted_char);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/optional.h"
|
||||
#include "ui/events/keycodes/keyboard_codes.h"
|
||||
|
||||
namespace electron {
|
||||
@@ -15,9 +16,11 @@ namespace electron {
|
||||
// pressed.
|
||||
ui::KeyboardCode KeyboardCodeFromCharCode(char16_t c, bool* shifted);
|
||||
|
||||
// Return key code of the |str|, and also determine whether the SHIFT key is
|
||||
// Return key code of the |str|, if the original key is a shifted character,
|
||||
// for example + and /, set it in |shifted_char|.
|
||||
// pressed.
|
||||
ui::KeyboardCode KeyboardCodeFromStr(const std::string& str, bool* shifted);
|
||||
ui::KeyboardCode KeyboardCodeFromStr(const std::string& str,
|
||||
base::Optional<char16_t>* shifted_char);
|
||||
|
||||
} // namespace electron
|
||||
|
||||
|
||||
@@ -489,9 +489,13 @@ node::Environment* NodeBindings::CreateEnvironment(
|
||||
// Node.js requires that microtask checkpoints be explicitly invoked.
|
||||
is.policy = v8::MicrotasksPolicy::kExplicit;
|
||||
} else {
|
||||
// Match Blink's behavior by allowing microtasks invocation to be controlled
|
||||
// by MicrotasksScope objects.
|
||||
is.policy = v8::MicrotasksPolicy::kScoped;
|
||||
// Blink expects the microtasks policy to be kScoped, but Node.js expects it
|
||||
// to be kExplicit. In the renderer, there can be many contexts within the
|
||||
// same isolate, so we don't want to change the existing policy here, which
|
||||
// could be either kExplicit or kScoped depending on whether we're executing
|
||||
// from within a Node.js or a Blink entrypoint. Instead, the policy is
|
||||
// toggled to kExplicit when entering Node.js through UvRunOnce.
|
||||
is.policy = context->GetIsolate()->GetMicrotasksPolicy();
|
||||
|
||||
// We do not want to use Node.js' message listener as it interferes with
|
||||
// Blink's.
|
||||
|
||||
@@ -688,7 +688,7 @@ void Initialize(v8::Local<v8::Object> exports,
|
||||
&electron::api::OverrideGlobalPropertyFromIsolatedWorld);
|
||||
dict.SetMethod("_isCalledFromMainWorld",
|
||||
&electron::api::IsCalledFromMainWorld);
|
||||
#ifdef DCHECK_IS_ON
|
||||
#if DCHECK_IS_ON()
|
||||
dict.Set("_isDebug", true);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "content/public/renderer/render_frame.h"
|
||||
#include "electron/buildflags/buildflags.h"
|
||||
#include "shell/common/api/electron_bindings.h"
|
||||
#include "shell/common/asar/asar_util.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/event_emitter_caller.h"
|
||||
#include "shell/common/node_bindings.h"
|
||||
@@ -40,9 +39,7 @@ ElectronRendererClient::ElectronRendererClient()
|
||||
NodeBindings::Create(NodeBindings::BrowserEnvironment::kRenderer)),
|
||||
electron_bindings_(new ElectronBindings(node_bindings_->uv_loop())) {}
|
||||
|
||||
ElectronRendererClient::~ElectronRendererClient() {
|
||||
asar::ClearArchives();
|
||||
}
|
||||
ElectronRendererClient::~ElectronRendererClient() = default;
|
||||
|
||||
void ElectronRendererClient::RenderFrameCreated(
|
||||
content::RenderFrame* render_frame) {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "base/lazy_instance.h"
|
||||
#include "base/threading/thread_local.h"
|
||||
#include "shell/common/api/electron_bindings.h"
|
||||
#include "shell/common/asar/asar_util.h"
|
||||
#include "shell/common/gin_helper/event_emitter_caller.h"
|
||||
#include "shell/common/node_bindings.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
@@ -39,7 +38,6 @@ WebWorkerObserver::~WebWorkerObserver() {
|
||||
lazy_tls.Pointer()->Set(nullptr);
|
||||
node::FreeEnvironment(node_bindings_->uv_env());
|
||||
node::FreeIsolateData(node_bindings_->isolate_data());
|
||||
asar::ClearArchives();
|
||||
}
|
||||
|
||||
void WebWorkerObserver::WorkerScriptReadyForEvaluation(
|
||||
|
||||
@@ -1418,15 +1418,12 @@ describe('BrowserWindow module', () => {
|
||||
describe('BrowserWindow.setAlwaysOnTop(flag, level)', () => {
|
||||
let w = null as unknown as BrowserWindow;
|
||||
|
||||
afterEach(closeAllWindows);
|
||||
|
||||
beforeEach(() => {
|
||||
w = new BrowserWindow({ show: true });
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closeWindow(w);
|
||||
w = null as unknown as BrowserWindow;
|
||||
});
|
||||
|
||||
it('sets the window as always on top', () => {
|
||||
expect(w.isAlwaysOnTop()).to.be.false('is alwaysOnTop');
|
||||
w.setAlwaysOnTop(true, 'screen-saver');
|
||||
@@ -1454,6 +1451,16 @@ describe('BrowserWindow module', () => {
|
||||
const [, alwaysOnTop] = await alwaysOnTopChanged;
|
||||
expect(alwaysOnTop).to.be.true('is not alwaysOnTop');
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('honors the alwaysOnTop level of a child window', () => {
|
||||
w = new BrowserWindow({ show: false });
|
||||
const c = new BrowserWindow({ parent: w });
|
||||
c.setAlwaysOnTop(true, 'screen-saver');
|
||||
|
||||
expect(w.isAlwaysOnTop()).to.be.false();
|
||||
expect(c.isAlwaysOnTop()).to.be.true('child is not always on top');
|
||||
expect((c as any)._getAlwaysOnTopLevel()).to.equal('screen-saver');
|
||||
});
|
||||
});
|
||||
|
||||
describe('preconnect feature', () => {
|
||||
@@ -1597,6 +1604,13 @@ describe('BrowserWindow module', () => {
|
||||
w.setVibrancy('' as any);
|
||||
}).to.not.throw();
|
||||
});
|
||||
|
||||
it('does not crash if vibrancy is set to an invalid value', () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
expect(() => {
|
||||
w.setVibrancy('i-am-not-a-valid-vibrancy-type' as any);
|
||||
}).to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
ifdescribe(process.platform === 'darwin')('trafficLightPosition', () => {
|
||||
|
||||
@@ -462,9 +462,9 @@ describe('MenuItems', () => {
|
||||
{ label: 'text', accelerator: 'Alt+A' }
|
||||
]);
|
||||
|
||||
expect(menu.getAcceleratorTextAt(0)).to.equal(isDarwin() ? '⌘A' : 'Ctrl+A');
|
||||
expect(menu.getAcceleratorTextAt(1)).to.equal(isDarwin() ? '⇧A' : 'Shift+A');
|
||||
expect(menu.getAcceleratorTextAt(2)).to.equal(isDarwin() ? '⌥A' : 'Alt+A');
|
||||
expect(menu.getAcceleratorTextAt(0)).to.equal(isDarwin() ? 'Command+A' : 'Ctrl+A');
|
||||
expect(menu.getAcceleratorTextAt(1)).to.equal('Shift+A');
|
||||
expect(menu.getAcceleratorTextAt(2)).to.equal('Alt+A');
|
||||
});
|
||||
|
||||
it('should display modifiers correctly for special keys', () => {
|
||||
@@ -474,9 +474,9 @@ describe('MenuItems', () => {
|
||||
{ label: 'text', accelerator: 'Alt+Tab' }
|
||||
]);
|
||||
|
||||
expect(menu.getAcceleratorTextAt(0)).to.equal(isDarwin() ? '⌘⇥' : 'Ctrl+Tab');
|
||||
expect(menu.getAcceleratorTextAt(1)).to.equal(isDarwin() ? '⇧⇥' : 'Shift+Tab');
|
||||
expect(menu.getAcceleratorTextAt(2)).to.equal(isDarwin() ? '⌥⇥' : 'Alt+Tab');
|
||||
expect(menu.getAcceleratorTextAt(0)).to.equal(isDarwin() ? 'Command+Tab' : 'Ctrl+Tab');
|
||||
expect(menu.getAcceleratorTextAt(1)).to.equal('Shift+Tab');
|
||||
expect(menu.getAcceleratorTextAt(2)).to.equal('Alt+Tab');
|
||||
});
|
||||
|
||||
it('should not display modifiers twice', () => {
|
||||
@@ -485,18 +485,26 @@ describe('MenuItems', () => {
|
||||
{ label: 'text', accelerator: 'Shift+Shift+Tab' }
|
||||
]);
|
||||
|
||||
expect(menu.getAcceleratorTextAt(0)).to.equal(isDarwin() ? '⇧A' : 'Shift+A');
|
||||
expect(menu.getAcceleratorTextAt(1)).to.equal(isDarwin() ? '⇧⇥' : 'Shift+Tab');
|
||||
expect(menu.getAcceleratorTextAt(0)).to.equal('Shift+A');
|
||||
expect(menu.getAcceleratorTextAt(1)).to.equal('Shift+Tab');
|
||||
});
|
||||
|
||||
it('should display correctly for edge cases', () => {
|
||||
it('should display correctly for shifted keys', () => {
|
||||
const menu = Menu.buildFromTemplate([
|
||||
{ label: 'text', accelerator: 'Control+Shift+=' },
|
||||
{ label: 'text', accelerator: 'Control+Plus' }
|
||||
{ label: 'text', accelerator: 'Control+Plus' },
|
||||
{ label: 'text', accelerator: 'Control+Shift+3' },
|
||||
{ label: 'text', accelerator: 'Control+#' },
|
||||
{ label: 'text', accelerator: 'Control+Shift+/' },
|
||||
{ label: 'text', accelerator: 'Control+?' }
|
||||
]);
|
||||
|
||||
expect(menu.getAcceleratorTextAt(0)).to.equal(isDarwin() ? '⌃⇧=' : 'Ctrl+Shift+=');
|
||||
expect(menu.getAcceleratorTextAt(1)).to.equal(isDarwin() ? '⌃⇧=' : 'Ctrl+Shift+=');
|
||||
expect(menu.getAcceleratorTextAt(0)).to.equal('Ctrl+Shift+=');
|
||||
expect(menu.getAcceleratorTextAt(1)).to.equal('Ctrl++');
|
||||
expect(menu.getAcceleratorTextAt(2)).to.equal('Ctrl+Shift+3');
|
||||
expect(menu.getAcceleratorTextAt(3)).to.equal('Ctrl+#');
|
||||
expect(menu.getAcceleratorTextAt(4)).to.equal('Ctrl+Shift+/');
|
||||
expect(menu.getAcceleratorTextAt(5)).to.equal('Ctrl+?');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('webRequest module', () => {
|
||||
res.setHeader('Location', 'http://' + req.rawHeaders[1]);
|
||||
res.end();
|
||||
} else if (req.url === '/contentDisposition') {
|
||||
res.setHeader('content-disposition', [' attachement; filename=aa%E4%B8%ADaa.txt']);
|
||||
res.setHeader('content-disposition', [' attachment; filename=aa%E4%B8%ADaa.txt']);
|
||||
const content = req.url;
|
||||
res.end(content);
|
||||
} else {
|
||||
@@ -214,6 +214,18 @@ describe('webRequest module', () => {
|
||||
await ajax(defaultURL);
|
||||
});
|
||||
|
||||
it('leaves headers unchanged when no requestHeaders in callback', async () => {
|
||||
let originalRequestHeaders: Record<string, string>;
|
||||
ses.webRequest.onBeforeSendHeaders((details, callback) => {
|
||||
originalRequestHeaders = details.requestHeaders;
|
||||
callback({});
|
||||
});
|
||||
ses.webRequest.onSendHeaders((details) => {
|
||||
expect(details.requestHeaders).to.deep.equal(originalRequestHeaders);
|
||||
});
|
||||
await ajax(defaultURL);
|
||||
});
|
||||
|
||||
it('works with file:// protocol', async () => {
|
||||
const requestHeaders = {
|
||||
Test: 'header'
|
||||
@@ -306,11 +318,11 @@ describe('webRequest module', () => {
|
||||
|
||||
it('does not change content-disposition header by default', async () => {
|
||||
ses.webRequest.onHeadersReceived((details, callback) => {
|
||||
expect(details.responseHeaders!['content-disposition']).to.deep.equal([' attachement; filename=aa中aa.txt']);
|
||||
expect(details.responseHeaders!['content-disposition']).to.deep.equal([' attachment; filename=aa中aa.txt']);
|
||||
callback({});
|
||||
});
|
||||
const { data, headers } = await ajax(defaultURL + 'contentDisposition');
|
||||
expect(headers).to.match(/^content-disposition: attachement; filename=aa%E4%B8%ADaa.txt$/m);
|
||||
expect(headers).to.match(/^content-disposition: attachment; filename=aa%E4%B8%ADaa.txt$/m);
|
||||
expect(data).to.equal('/contentDisposition');
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<html>
|
||||
<body>
|
||||
<script>
|
||||
// `setImmediate` schedules a function to be run on the next UV tick, which
|
||||
// ensures that `window.open` is run from `UvRunOnce()`.
|
||||
setImmediate(async () => {
|
||||
if (!location.hash) {
|
||||
const p = new Promise(resolve => {
|
||||
window.addEventListener('message', resolve, { once: true });
|
||||
});
|
||||
window.open('#foo', '', 'nodeIntegration=no,show=no');
|
||||
const e = await p;
|
||||
|
||||
window.close();
|
||||
} else {
|
||||
window.opener.postMessage('foo', '*');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,21 @@
|
||||
const { app, BrowserWindow } = require('electron');
|
||||
|
||||
function createWindow () {
|
||||
const mainWindow = new BrowserWindow({
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
nativeWindowOpen: true
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.on('close', () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
mainWindow.loadFile('index.html');
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
});
|
||||
1
spec-main/fixtures/dogs-running.txt
Normal file
1
spec-main/fixtures/dogs-running.txt
Normal file
File diff suppressed because one or more lines are too long
@@ -263,6 +263,12 @@ describe('webContents.setWindowOpenHandler', () => {
|
||||
|
||||
browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
|
||||
});
|
||||
|
||||
it('does not hang parent window when denying window.open', async () => {
|
||||
browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'deny' }));
|
||||
browserWindow.webContents.executeJavaScript("window.open('https://127.0.0.1')");
|
||||
expect(await browserWindow.webContents.executeJavaScript('42')).to.equal(42);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user