mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
Compare commits
39 Commits
chore/add-
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b417696d6b | ||
|
|
4203d7688f | ||
|
|
62e637275a | ||
|
|
28c0eb29df | ||
|
|
8a730e2aec | ||
|
|
044be7ce40 | ||
|
|
7245c6a3f0 | ||
|
|
b484b0bde9 | ||
|
|
6c8a910232 | ||
|
|
cc3d4f5f58 | ||
|
|
b711ce7b04 | ||
|
|
adf9a6e303 | ||
|
|
6744293e96 | ||
|
|
0d3342debf | ||
|
|
157cdac4b9 | ||
|
|
4dfada86ce | ||
|
|
df81a1d4ac | ||
|
|
c3e3958668 | ||
|
|
afd5fb4a60 | ||
|
|
8679522922 | ||
|
|
0828de3ccd | ||
|
|
6b5a4ff66c | ||
|
|
ca28023d4d | ||
|
|
e60441ad60 | ||
|
|
a189425373 | ||
|
|
7eccea1315 | ||
|
|
2e74ad2c68 | ||
|
|
9ba299afff | ||
|
|
6df2228ea0 | ||
|
|
a29674e4cf | ||
|
|
81dd0f42e1 | ||
|
|
6aaf490aa5 | ||
|
|
b8f25c4ced | ||
|
|
9fafc81e88 | ||
|
|
4d05010945 | ||
|
|
c3189e9886 | ||
|
|
983ebdd6de | ||
|
|
b9c08ef9c2 | ||
|
|
9f3cc9122c |
12
.github/actions/build-electron/action.yml
vendored
12
.github/actions/build-electron/action.yml
vendored
@@ -228,7 +228,17 @@ runs:
|
||||
if: ${{ inputs.is-release == 'true' }}
|
||||
run: |
|
||||
cd src
|
||||
gn gen out/ffmpeg --args="import(\"//electron/build/args/ffmpeg.gn\") use_remoteexec=true use_siso=true $GN_EXTRA_ARGS"
|
||||
# Reuse the hermetic mac_sdk_path that `e build` wrote for out/Default so
|
||||
# out/ffmpeg builds against the same SDK instead of the runner's system Xcode.
|
||||
# The path has to live under root_build_dir, so copy the symlink tree and
|
||||
# rewrite Default -> ffmpeg.
|
||||
MAC_SDK_ARG=""
|
||||
if [ "$(uname)" = "Darwin" ]; then
|
||||
mkdir -p out/ffmpeg
|
||||
cp -a out/Default/xcode_links out/ffmpeg/
|
||||
MAC_SDK_ARG=$(sed -n 's|^\(mac_sdk_path = "//out/\)Default/|\1ffmpeg/|p' out/Default/args.gn)
|
||||
fi
|
||||
gn gen out/ffmpeg --args="import(\"//electron/build/args/ffmpeg.gn\") use_remoteexec=true use_siso=true $MAC_SDK_ARG $GN_EXTRA_ARGS"
|
||||
e build --target electron:electron_ffmpeg_zip -C ../../out/ffmpeg
|
||||
- name: Remove Clang problem matcher
|
||||
shell: bash
|
||||
|
||||
20
.github/actions/checkout/action.yml
vendored
20
.github/actions/checkout/action.yml
vendored
@@ -191,19 +191,31 @@ runs:
|
||||
# only permits .tar/.tgz so we keep the extension and decode on restore.
|
||||
tar -cf - src | zstd -T0 --long=30 -f -o $CACHE_FILE
|
||||
echo "Compressed src to $(du -sh $CACHE_FILE | cut -f1 -d' ')"
|
||||
cp ./$CACHE_FILE $CACHE_DRIVE/
|
||||
- name: Persist Src Cache
|
||||
if: ${{ steps.check-cache.outputs.cache_exists == 'false' && inputs.use-cache == 'true' }}
|
||||
shell: bash
|
||||
run: |
|
||||
final_cache_path=$CACHE_DRIVE/$CACHE_FILE
|
||||
# Upload to a run-unique temp name first so concurrent readers never
|
||||
# observe a partially-written file, and an interrupted copy can't leave
|
||||
# a truncated file at the final path. Orphaned temp files get swept by
|
||||
# the clean-orphaned-cache-uploads workflow.
|
||||
tmp_cache_path=$final_cache_path.upload-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}
|
||||
echo "Uploading to temp path: $tmp_cache_path"
|
||||
cp ./$CACHE_FILE $tmp_cache_path
|
||||
|
||||
echo "Using cache key: $DEPSHASH"
|
||||
echo "Checking path: $final_cache_path"
|
||||
if [ -f "$final_cache_path" ]; then
|
||||
echo "Cache already persisted at $final_cache_path by a concurrent run; discarding ours"
|
||||
rm -f $tmp_cache_path
|
||||
else
|
||||
mv -f $tmp_cache_path $final_cache_path
|
||||
echo "Cache key persisted in $final_cache_path"
|
||||
fi
|
||||
|
||||
if [ ! -f "$final_cache_path" ]; then
|
||||
echo "Cache key not found"
|
||||
exit 1
|
||||
else
|
||||
echo "Cache key persisted in $final_cache_path"
|
||||
fi
|
||||
- name: Wait for active SSH sessions
|
||||
shell: bash
|
||||
|
||||
@@ -15,7 +15,7 @@ runs:
|
||||
git config --global core.preloadindex true
|
||||
git config --global core.longpaths true
|
||||
fi
|
||||
export BUILD_TOOLS_SHA=a0cc95a1884a631559bcca0c948465b725d9295a
|
||||
export BUILD_TOOLS_SHA=1b7bd25dae4a780bb3170fff56c9327b53aaf7eb
|
||||
npm i -g @electron/build-tools
|
||||
# Update depot_tools to ensure python
|
||||
e d update_depot_tools
|
||||
@@ -29,4 +29,4 @@ runs:
|
||||
else
|
||||
echo "$HOME/.electron_build_tools/third_party/depot_tools" >> $GITHUB_PATH
|
||||
echo "$HOME/.electron_build_tools/third_party/depot_tools/python-bin" >> $GITHUB_PATH
|
||||
fi
|
||||
fi
|
||||
|
||||
29
.github/workflows/build.yml
vendored
29
.github/workflows/build.yml
vendored
@@ -442,34 +442,7 @@ jobs:
|
||||
contents: read
|
||||
needs: [docs-only, macos-x64, macos-arm64, linux-x64, linux-x64-asan, linux-arm, linux-arm64, windows-x64, windows-x86, windows-arm64]
|
||||
if: always() && github.repository == 'electron/electron' && !contains(needs.*.result, 'failure')
|
||||
steps:
|
||||
steps:
|
||||
- name: GitHub Actions Jobs Done
|
||||
run: |
|
||||
echo "All GitHub Actions Jobs are done"
|
||||
|
||||
check-signed-commits:
|
||||
name: Check signed commits in green PR
|
||||
needs: gha-done
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'needs-signed-commits')}}
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check signed commits in PR
|
||||
uses: 1Password/check-signed-commits-action@ed2885f3ed2577a4f5d3c3fe895432a557d23d52 # v1
|
||||
with:
|
||||
comment: |
|
||||
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
|
||||
for all incoming PRs. To get your PR merged, please sign those commits
|
||||
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
|
||||
(`git push --force-with-lease`)
|
||||
|
||||
For more information on signing commits, see GitHub's documentation on [Telling Git about your signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key).
|
||||
|
||||
- name: Remove needs-signed-commits label
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
gh pr edit $PR_URL --remove-label needs-signed-commits
|
||||
|
||||
32
.github/workflows/clean-orphaned-cache-uploads.yml
vendored
Normal file
32
.github/workflows/clean-orphaned-cache-uploads.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Clean Orphaned Cache Uploads
|
||||
|
||||
# Description:
|
||||
# Sweeps orphaned in-flight upload temp files left on the src-cache volumes
|
||||
# by checkout/action.yml when its cp-to-share step dies before the rename.
|
||||
# A successful upload finishes in minutes, so anything older than 4h is dead.
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 */4 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
clean-orphaned-uploads:
|
||||
if: github.repository == 'electron/electron'
|
||||
runs-on: electron-arc-centralus-linux-amd64-32core
|
||||
permissions:
|
||||
contents: read
|
||||
container:
|
||||
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
|
||||
options: --user root
|
||||
volumes:
|
||||
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
||||
- /mnt/win-cache:/mnt/win-cache
|
||||
steps:
|
||||
- name: Remove Orphaned Upload Temp Files
|
||||
shell: bash
|
||||
run: |
|
||||
find /mnt/cross-instance-cache -maxdepth 1 -type f -name '*.tar.upload-*' -mmin +240 -print -delete
|
||||
find /mnt/win-cache -maxdepth 1 -type f -name '*.tar.upload-*' -mmin +240 -print -delete
|
||||
2
.github/workflows/pr-triage-automation.yml
vendored
2
.github/workflows/pr-triage-automation.yml
vendored
@@ -17,11 +17,13 @@ jobs:
|
||||
name: Set status to Needs Review
|
||||
if: >-
|
||||
(github.event_name == 'pull_request_target'
|
||||
&& github.event.pull_request.state == 'open'
|
||||
&& github.event.pull_request.draft != true
|
||||
&& !contains(github.event.pull_request.labels.*.name, 'wip ⚒')
|
||||
&& (github.event.action == 'synchronize' || github.event.action == 'review_requested'))
|
||||
|| (github.event_name == 'issue_comment'
|
||||
&& github.event.issue.pull_request
|
||||
&& github.event.issue.state == 'open'
|
||||
&& !contains(github.event.issue.labels.*.name, 'wip ⚒')
|
||||
&& github.event.comment.user.login == github.event.issue.user.login)
|
||||
runs-on: ubuntu-slim
|
||||
|
||||
2
.github/workflows/pull-request-labeled.yml
vendored
2
.github/workflows/pull-request-labeled.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
field-value: ✅ Reviewed
|
||||
pull-request-labeled-ai-pr:
|
||||
name: ai-pr label added
|
||||
if: github.event.label.name == 'ai-pr'
|
||||
if: github.event.label.name == 'ai-pr' && github.event.pull_request.state != 'closed'
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
steps:
|
||||
|
||||
@@ -13,7 +13,6 @@ permissions: {}
|
||||
jobs:
|
||||
check-signed-commits:
|
||||
name: Check signed commits in PR
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'needs-signed-commits')}}
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -23,9 +22,9 @@ jobs:
|
||||
uses: 1Password/check-signed-commits-action@ed2885f3ed2577a4f5d3c3fe895432a557d23d52 # v1
|
||||
with:
|
||||
comment: |
|
||||
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
|
||||
for all incoming PRs. To get your PR merged, please sign those commits
|
||||
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
|
||||
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
|
||||
for all incoming PRs. To get your PR merged, please sign those commits
|
||||
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
|
||||
(`git push --force-with-lease`)
|
||||
|
||||
For more information on signing commits, see GitHub's documentation on [Telling Git about your signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key).
|
||||
@@ -37,3 +36,11 @@ jobs:
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
gh pr edit $PR_URL --add-label needs-signed-commits
|
||||
|
||||
- name: Remove needs-signed-commits label
|
||||
if: ${{ success() && contains(github.event.pull_request.labels.*.name, 'needs-signed-commits') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
gh pr edit $PR_URL --remove-label needs-signed-commits
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"arrowParens": "always",
|
||||
"bracketSpacing": true,
|
||||
"endOfLine": "lf",
|
||||
"sortImports": {
|
||||
"newlinesBetween": true,
|
||||
"groups": [
|
||||
"electron-internal",
|
||||
"electron-scoped",
|
||||
"electron",
|
||||
"external",
|
||||
"builtin",
|
||||
["sibling", "parent"],
|
||||
"index",
|
||||
"type",
|
||||
"unknown"
|
||||
],
|
||||
"customGroups": [
|
||||
{
|
||||
"groupName": "electron-internal",
|
||||
"elementNamePattern": ["@electron/internal", "@electron/internal/**"]
|
||||
},
|
||||
{
|
||||
"groupName": "electron-scoped",
|
||||
"elementNamePattern": ["@electron/**"]
|
||||
},
|
||||
{
|
||||
"groupName": "electron",
|
||||
"elementNamePattern": ["electron", "electron/**"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"node_modules",
|
||||
"out",
|
||||
"ts-gen",
|
||||
"spec/node_modules",
|
||||
"spec/fixtures/native-addon",
|
||||
".github/workflows/node_modules",
|
||||
"docs/fiddles",
|
||||
"shell/browser/resources/win/resource.h",
|
||||
"shell/common/node_includes.h",
|
||||
"spec/fixtures/pages/jquery-3.6.0.min.js"
|
||||
]
|
||||
}
|
||||
1
BUILD.gn
1
BUILD.gn
@@ -775,6 +775,7 @@ source_set("electron_lib") {
|
||||
"//components/zoom",
|
||||
"//extensions/browser",
|
||||
"//extensions/browser/api:api_provider",
|
||||
"//extensions/browser/mime_handler:stream_info",
|
||||
"//extensions/browser/updater",
|
||||
"//extensions/common",
|
||||
"//extensions/common:core_api_provider",
|
||||
|
||||
2
DEPS
2
DEPS
@@ -2,7 +2,7 @@ gclient_gn_args_from = 'src'
|
||||
|
||||
vars = {
|
||||
'chromium_version':
|
||||
'148.0.7763.0',
|
||||
'148.0.7778.0',
|
||||
'node_version':
|
||||
'v24.14.1',
|
||||
'nan_version':
|
||||
|
||||
@@ -8,13 +8,10 @@ const path = require('node:path');
|
||||
const electronRoot = path.resolve(__dirname, '../..');
|
||||
|
||||
class AccessDependenciesPlugin {
|
||||
apply(compiler) {
|
||||
compiler.hooks.compilation.tap('AccessDependenciesPlugin', (compilation) => {
|
||||
compilation.hooks.finishModules.tap('AccessDependenciesPlugin', (modules) => {
|
||||
const filePaths = modules
|
||||
.map((m) => m.resource)
|
||||
.filter((p) => p)
|
||||
.map((p) => path.relative(electronRoot, p));
|
||||
apply (compiler) {
|
||||
compiler.hooks.compilation.tap('AccessDependenciesPlugin', compilation => {
|
||||
compilation.hooks.finishModules.tap('AccessDependenciesPlugin', modules => {
|
||||
const filePaths = modules.map(m => m.resource).filter(p => p).map(p => path.relative(electronRoot, p));
|
||||
console.info(JSON.stringify(filePaths));
|
||||
});
|
||||
});
|
||||
@@ -34,14 +31,7 @@ module.exports = ({
|
||||
entry = path.resolve(electronRoot, 'lib', target, 'init.js');
|
||||
}
|
||||
|
||||
const electronAPIFile = path.resolve(
|
||||
electronRoot,
|
||||
'lib',
|
||||
loadElectronFromAlternateTarget || target,
|
||||
'api',
|
||||
'exports',
|
||||
'electron.ts'
|
||||
);
|
||||
const electronAPIFile = path.resolve(electronRoot, 'lib', loadElectronFromAlternateTarget || target, 'api', 'exports', 'electron.ts');
|
||||
|
||||
return (env = {}, argv = {}) => {
|
||||
const onlyPrintingGraph = !!env.PRINT_WEBPACK_GRAPH;
|
||||
@@ -71,59 +61,49 @@ module.exports = ({
|
||||
}
|
||||
|
||||
if (targetDeletesNodeGlobals) {
|
||||
plugins.push(
|
||||
new webpack.ProvidePlugin({
|
||||
Buffer: ['@electron/internal/common/webpack-provider', 'Buffer'],
|
||||
global: ['@electron/internal/common/webpack-provider', '_global'],
|
||||
process: ['@electron/internal/common/webpack-provider', 'process']
|
||||
})
|
||||
);
|
||||
plugins.push(new webpack.ProvidePlugin({
|
||||
Buffer: ['@electron/internal/common/webpack-provider', 'Buffer'],
|
||||
global: ['@electron/internal/common/webpack-provider', '_global'],
|
||||
process: ['@electron/internal/common/webpack-provider', 'process']
|
||||
}));
|
||||
}
|
||||
|
||||
// Webpack 5 no longer polyfills process or Buffer.
|
||||
if (!alwaysHasNode) {
|
||||
plugins.push(
|
||||
new webpack.ProvidePlugin({
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
process: 'process/browser'
|
||||
})
|
||||
);
|
||||
plugins.push(new webpack.ProvidePlugin({
|
||||
Buffer: ['buffer', 'Buffer'],
|
||||
process: 'process/browser'
|
||||
}));
|
||||
}
|
||||
|
||||
plugins.push(
|
||||
new webpack.ProvidePlugin({
|
||||
Promise: ['@electron/internal/common/webpack-globals-provider', 'Promise']
|
||||
})
|
||||
);
|
||||
plugins.push(new webpack.ProvidePlugin({
|
||||
Promise: ['@electron/internal/common/webpack-globals-provider', 'Promise']
|
||||
}));
|
||||
|
||||
plugins.push(new webpack.DefinePlugin(defines));
|
||||
|
||||
if (wrapInitWithProfilingTimeout) {
|
||||
plugins.push(
|
||||
new WrapperPlugin({
|
||||
header: 'function ___electron_webpack_init__() {',
|
||||
footer: `
|
||||
plugins.push(new WrapperPlugin({
|
||||
header: 'function ___electron_webpack_init__() {',
|
||||
footer: `
|
||||
};
|
||||
if ((globalThis.process || binding.process).argv.includes("--profile-electron-init")) {
|
||||
setTimeout(___electron_webpack_init__, 0);
|
||||
} else {
|
||||
___electron_webpack_init__();
|
||||
}`
|
||||
})
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
if (wrapInitWithTryCatch) {
|
||||
plugins.push(
|
||||
new WrapperPlugin({
|
||||
header: 'try {',
|
||||
footer: `
|
||||
plugins.push(new WrapperPlugin({
|
||||
header: 'try {',
|
||||
footer: `
|
||||
} catch (err) {
|
||||
console.error('Electron ${outputFilename} script failed to run');
|
||||
console.error(err);
|
||||
}`
|
||||
})
|
||||
);
|
||||
}));
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -153,26 +133,23 @@ if ((globalThis.process || binding.process).argv.includes("--profile-electron-in
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: (moduleName) => !onlyPrintingGraph && ignoredModules.includes(moduleName),
|
||||
loader: 'null-loader'
|
||||
},
|
||||
{
|
||||
test: /\.ts$/,
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
configFile: path.resolve(electronRoot, 'tsconfig.electron.json'),
|
||||
transpileOnly: onlyPrintingGraph,
|
||||
ignoreDiagnostics: [
|
||||
// File '{0}' is not under 'rootDir' '{1}'.
|
||||
6059,
|
||||
// Private field '{0}' must be declared in an enclosing class.
|
||||
1111
|
||||
]
|
||||
}
|
||||
rules: [{
|
||||
test: (moduleName) => !onlyPrintingGraph && ignoredModules.includes(moduleName),
|
||||
loader: 'null-loader'
|
||||
}, {
|
||||
test: /\.ts$/,
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
configFile: path.resolve(electronRoot, 'tsconfig.electron.json'),
|
||||
transpileOnly: onlyPrintingGraph,
|
||||
ignoreDiagnostics: [
|
||||
// File '{0}' is not under 'rootDir' '{1}'.
|
||||
6059,
|
||||
// Private field '{0}' must be declared in an enclosing class.
|
||||
1111
|
||||
]
|
||||
}
|
||||
]
|
||||
}]
|
||||
},
|
||||
node: {
|
||||
__dirname: false,
|
||||
|
||||
@@ -144,6 +144,8 @@ static_library("chrome") {
|
||||
"//chrome/browser/ui/views/overlay/toggle_camera_button.h",
|
||||
"//chrome/browser/ui/views/overlay/toggle_microphone_button.cc",
|
||||
"//chrome/browser/ui/views/overlay/toggle_microphone_button.h",
|
||||
"//chrome/browser/ui/views/overlay/toggle_mute_button.cc",
|
||||
"//chrome/browser/ui/views/overlay/toggle_mute_button.h",
|
||||
"//chrome/browser/ui/views/overlay/video_overlay_window_views.cc",
|
||||
"//chrome/browser/ui/views/overlay/video_overlay_window_views.h",
|
||||
"//chrome/browser/ui/views/picture_in_picture/picture_in_picture_bounds_change_animation.cc",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { shell } from 'electron/common';
|
||||
import { app, dialog, BrowserWindow, ipcMain } from 'electron/main';
|
||||
import { app, dialog, BrowserWindow, ipcMain, Menu } from 'electron/main';
|
||||
|
||||
import * as path from 'node:path';
|
||||
import * as url from 'node:url';
|
||||
@@ -11,7 +11,54 @@ app.on('window-all-closed', () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
function decorateURL(url: string) {
|
||||
const isMac = process.platform === 'darwin';
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const helpMenu: Electron.MenuItemConstructorOptions = {
|
||||
role: 'help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Learn More',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://electronjs.org');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Documentation',
|
||||
click: async () => {
|
||||
const version = process.versions.electron;
|
||||
await shell.openExternal(`https://github.com/electron/electron/tree/v${version}/docs#readme`);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Community Discussions',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://discord.gg/electronjs');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Search Issues',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://github.com/electron/electron/issues');
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const macAppMenu: Electron.MenuItemConstructorOptions = { role: 'appMenu' };
|
||||
const template: Electron.MenuItemConstructorOptions[] = [
|
||||
...(isMac ? [macAppMenu] : []),
|
||||
{ role: 'fileMenu' },
|
||||
{ role: 'editMenu' },
|
||||
{ role: 'viewMenu' },
|
||||
{ role: 'windowMenu' },
|
||||
helpMenu
|
||||
];
|
||||
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
|
||||
});
|
||||
|
||||
function decorateURL (url: string) {
|
||||
// safely add `?utm_source=default_app
|
||||
const parsedUrl = new URL(url);
|
||||
parsedUrl.searchParams.append('utm_source', 'default_app');
|
||||
@@ -21,12 +68,13 @@ function decorateURL(url: string) {
|
||||
// Find the shortest path to the electron binary
|
||||
const absoluteElectronPath = process.execPath;
|
||||
const relativeElectronPath = path.relative(process.cwd(), absoluteElectronPath);
|
||||
const electronPath =
|
||||
absoluteElectronPath.length < relativeElectronPath.length ? absoluteElectronPath : relativeElectronPath;
|
||||
const electronPath = absoluteElectronPath.length < relativeElectronPath.length
|
||||
? absoluteElectronPath
|
||||
: relativeElectronPath;
|
||||
|
||||
const indexPath = path.resolve(app.getAppPath(), 'index.html');
|
||||
|
||||
function isTrustedSender(webContents: Electron.WebContents) {
|
||||
function isTrustedSender (webContents: Electron.WebContents) {
|
||||
if (webContents !== (mainWindow && mainWindow.webContents)) {
|
||||
return false;
|
||||
}
|
||||
@@ -42,7 +90,7 @@ ipcMain.handle('bootstrap', (event) => {
|
||||
return isTrustedSender(event.sender) ? electronPath : null;
|
||||
});
|
||||
|
||||
async function createWindow(backgroundColor?: string) {
|
||||
async function createWindow (backgroundColor?: string) {
|
||||
await app.whenReady();
|
||||
|
||||
const options: Electron.BrowserWindowConstructorOptions = {
|
||||
@@ -67,7 +115,7 @@ async function createWindow(backgroundColor?: string) {
|
||||
mainWindow = new BrowserWindow(options);
|
||||
mainWindow.on('ready-to-show', () => mainWindow!.show());
|
||||
|
||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||
mainWindow.webContents.setWindowOpenHandler(details => {
|
||||
shell.openExternal(decorateURL(details.url));
|
||||
return { action: 'deny' };
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ type DefaultAppOptions = {
|
||||
interactive: boolean;
|
||||
abi: boolean;
|
||||
modules: string[];
|
||||
};
|
||||
}
|
||||
|
||||
// Parse command line options.
|
||||
const argv = process.argv.slice(1);
|
||||
@@ -74,7 +74,7 @@ if (option.modules.length > 0) {
|
||||
(Module as any)._preloadModules(option.modules);
|
||||
}
|
||||
|
||||
async function loadApplicationPackage(packagePath: string) {
|
||||
async function loadApplicationPackage (packagePath: string) {
|
||||
// Add a flag indicating app is started from default app.
|
||||
Object.defineProperty(process, 'defaultApp', {
|
||||
configurable: false,
|
||||
@@ -92,11 +92,9 @@ async function loadApplicationPackage(packagePath: string) {
|
||||
const emitWarning = process.emitWarning;
|
||||
try {
|
||||
process.emitWarning = () => {};
|
||||
packageJson = (
|
||||
await import(url.pathToFileURL(packageJsonPath).toString(), {
|
||||
with: { type: 'json' }
|
||||
})
|
||||
).default;
|
||||
packageJson = (await import(url.pathToFileURL(packageJsonPath).toString(), {
|
||||
with: { type: 'json' }
|
||||
})).default;
|
||||
} catch (e) {
|
||||
showErrorMessage(`Unable to parse ${packageJsonPath}\n\n${(e as Error).message}`);
|
||||
return;
|
||||
@@ -145,23 +143,23 @@ async function loadApplicationPackage(packagePath: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function showErrorMessage(message: string) {
|
||||
function showErrorMessage (message: string) {
|
||||
app.focus();
|
||||
dialog.showErrorBox('Error launching app', message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function loadApplicationByURL(appUrl: string) {
|
||||
async function loadApplicationByURL (appUrl: string) {
|
||||
const { loadURL } = await import('./default_app.js');
|
||||
loadURL(appUrl);
|
||||
}
|
||||
|
||||
async function loadApplicationByFile(appPath: string) {
|
||||
async function loadApplicationByFile (appPath: string) {
|
||||
const { loadFile } = await import('./default_app.js');
|
||||
loadFile(appPath);
|
||||
}
|
||||
|
||||
async function startRepl() {
|
||||
async function startRepl () {
|
||||
if (process.platform === 'win32') {
|
||||
console.error('Electron REPL not currently supported on Windows');
|
||||
process.exit(1);
|
||||
@@ -189,7 +187,7 @@ async function startRepl() {
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
function defineBuiltin(context: any, name: string, getter: Function) {
|
||||
function defineBuiltin (context: any, name: string, getter: Function) {
|
||||
const setReal = (val: any) => {
|
||||
// Deleting the property before re-assigning it disables the
|
||||
// getter/setter mechanism.
|
||||
@@ -227,42 +225,11 @@ async function startRepl() {
|
||||
// we only trigger custom tab-completion when no common words are
|
||||
// potentially matches.
|
||||
const commonWords = [
|
||||
'async',
|
||||
'await',
|
||||
'break',
|
||||
'case',
|
||||
'catch',
|
||||
'const',
|
||||
'continue',
|
||||
'debugger',
|
||||
'default',
|
||||
'delete',
|
||||
'do',
|
||||
'else',
|
||||
'export',
|
||||
'false',
|
||||
'finally',
|
||||
'for',
|
||||
'function',
|
||||
'if',
|
||||
'import',
|
||||
'in',
|
||||
'instanceof',
|
||||
'let',
|
||||
'new',
|
||||
'null',
|
||||
'return',
|
||||
'switch',
|
||||
'this',
|
||||
'throw',
|
||||
'true',
|
||||
'try',
|
||||
'typeof',
|
||||
'var',
|
||||
'void',
|
||||
'while',
|
||||
'with',
|
||||
'yield'
|
||||
'async', 'await', 'break', 'case', 'catch', 'const', 'continue',
|
||||
'debugger', 'default', 'delete', 'do', 'else', 'export', 'false',
|
||||
'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'let',
|
||||
'new', 'null', 'return', 'switch', 'this', 'throw', 'true', 'try',
|
||||
'typeof', 'var', 'void', 'while', 'with', 'yield'
|
||||
];
|
||||
|
||||
const electronBuiltins = [...Object.keys(electron), 'original-fs', 'electron'];
|
||||
|
||||
@@ -2,10 +2,10 @@ const { ipcRenderer, contextBridge } = require('electron/renderer');
|
||||
|
||||
const policy = window.trustedTypes.createPolicy('electron-default-app', {
|
||||
// we trust the SVG contents
|
||||
createHTML: (input) => input
|
||||
createHTML: input => input
|
||||
});
|
||||
|
||||
async function getOcticonSvg(name: string) {
|
||||
async function getOcticonSvg (name: string) {
|
||||
try {
|
||||
const response = await fetch(`octicon/${name}.svg`);
|
||||
const div = document.createElement('div');
|
||||
@@ -16,7 +16,7 @@ async function getOcticonSvg(name: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSVG(element: HTMLSpanElement) {
|
||||
async function loadSVG (element: HTMLSpanElement) {
|
||||
for (const cssClass of element.classList) {
|
||||
if (cssClass.startsWith('octicon-')) {
|
||||
const icon = await getOcticonSvg(cssClass.substr(8));
|
||||
@@ -32,9 +32,9 @@ async function loadSVG(element: HTMLSpanElement) {
|
||||
}
|
||||
}
|
||||
|
||||
async function initialize() {
|
||||
async function initialize () {
|
||||
const electronPath = await ipcRenderer.invoke('bootstrap');
|
||||
function replaceText(selector: string, text: string, link?: string) {
|
||||
function replaceText (selector: string, text: string, link?: string) {
|
||||
const element = document.querySelector<HTMLElement>(selector);
|
||||
if (element) {
|
||||
if (link) {
|
||||
@@ -51,11 +51,7 @@ async function initialize() {
|
||||
|
||||
replaceText('.electron-version', `Electron v${process.versions.electron}`, 'https://electronjs.org/docs');
|
||||
replaceText('.chrome-version', `Chromium v${process.versions.chrome}`, 'https://developer.chrome.com/docs/chromium');
|
||||
replaceText(
|
||||
'.node-version',
|
||||
`Node v${process.versions.node}`,
|
||||
`https://nodejs.org/docs/v${process.versions.node}/api`
|
||||
);
|
||||
replaceText('.node-version', `Node v${process.versions.node}`, `https://nodejs.org/docs/v${process.versions.node}/api`);
|
||||
replaceText('.v8-version', `v8 v${process.versions.v8}`, 'https://v8.dev/docs');
|
||||
replaceText('.command-example', `${electronPath} path-to-app`);
|
||||
|
||||
|
||||
@@ -148,3 +148,34 @@ added:
|
||||
-->
|
||||
|
||||
Unregisters all of the global shortcuts.
|
||||
|
||||
### `globalShortcut.setSuspended(suspended)`
|
||||
|
||||
<!--
|
||||
```YAML history
|
||||
added:
|
||||
- pr-url: https://github.com/electron/electron/pull/50425
|
||||
```
|
||||
-->
|
||||
|
||||
* `suspended` boolean - Whether global shortcut handling should be suspended.
|
||||
|
||||
Suspends or resumes global shortcut handling. When suspended, all registered
|
||||
global shortcuts will stop listening for key presses. When resumed, all
|
||||
previously registered shortcuts will begin listening again. New shortcut
|
||||
registrations will fail while handling is suspended.
|
||||
|
||||
This can be useful when you want to temporarily allow the user to press key
|
||||
combinations without your application intercepting them, for example while
|
||||
displaying a UI to rebind shortcuts.
|
||||
|
||||
### `globalShortcut.isSuspended()`
|
||||
|
||||
<!--
|
||||
```YAML history
|
||||
added:
|
||||
- pr-url: https://github.com/electron/electron/pull/50425
|
||||
```
|
||||
-->
|
||||
|
||||
Returns `boolean` - Whether global shortcut handling is currently suspended.
|
||||
|
||||
@@ -44,8 +44,8 @@ See [`Menu`](menu.md) for examples.
|
||||
menu items.
|
||||
* `registerAccelerator` boolean (optional) _Linux_ _Windows_ - If false, the accelerator won't be registered
|
||||
with the system, but it will still be displayed. Defaults to true.
|
||||
* `sharingItem` SharingItem (optional) _macOS_ - The item to share when the `role` is `shareMenu`.
|
||||
* `submenu` (MenuItemConstructorOptions[] | [Menu](menu.md)) (optional) - Should be specified
|
||||
* `sharingItem` [SharingItem](structures/sharing-item.md) (optional) _macOS_ - The item to share when the `role` is `shareMenu`.
|
||||
* `submenu` ([MenuItemConstructorOptions](#new-menuitemoptions)[] | [Menu](menu.md)) (optional) - Should be specified
|
||||
for `submenu` type menu items. If `submenu` is specified, the `type: 'submenu'` can be omitted.
|
||||
If the value is not a [`Menu`](menu.md) then it will be automatically converted to one using
|
||||
`Menu.buildFromTemplate`.
|
||||
@@ -89,7 +89,7 @@ A `Function` that is fired when the MenuItem receives a click event.
|
||||
It can be called with `menuItem.click(event, focusedWindow, focusedWebContents)`.
|
||||
|
||||
* `event` [KeyboardEvent](structures/keyboard-event.md)
|
||||
* `focusedWindow` [BaseWindow](browser-window.md)
|
||||
* `focusedWindow` [BaseWindow](base-window.md)
|
||||
* `focusedWebContents` [WebContents](web-contents.md)
|
||||
|
||||
#### `menuItem.submenu`
|
||||
@@ -110,11 +110,11 @@ A `string` (optional) indicating the item's role, if set. Can be `undo`, `redo`,
|
||||
|
||||
#### `menuItem.accelerator`
|
||||
|
||||
An `Accelerator | null` indicating the item's accelerator, if set.
|
||||
An [`Accelerator | null`](../tutorial/keyboard-shortcuts.md#accelerators) indicating the item's accelerator, if set.
|
||||
|
||||
#### `menuItem.userAccelerator` _Readonly_ _macOS_
|
||||
|
||||
An `Accelerator | null` indicating the item's [user-assigned accelerator](https://developer.apple.com/documentation/appkit/nsmenuitem/1514850-userkeyequivalent?language=objc) for the menu item.
|
||||
An [`Accelerator | null`](../tutorial/keyboard-shortcuts.md#accelerators) indicating the item's [user-assigned accelerator](https://developer.apple.com/documentation/appkit/nsmenuitem/1514850-userkeyequivalent?language=objc) for the menu item.
|
||||
|
||||
> [!NOTE]
|
||||
> This property is only initialized after the `MenuItem` has been added to a `Menu`. Either via `Menu.buildFromTemplate` or via `Menu.append()/insert()`. Accessing before initialization will just return `null`.
|
||||
@@ -170,7 +170,7 @@ This property can be dynamically changed.
|
||||
|
||||
#### `menuItem.sharingItem` _macOS_
|
||||
|
||||
A `SharingItem` indicating the item to share when the `role` is `shareMenu`.
|
||||
A [`SharingItem`](structures/sharing-item.md) indicating the item to share when the `role` is `shareMenu`.
|
||||
|
||||
This property can be dynamically changed.
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ this has the additional effect of removing the menu bar from the window.
|
||||
|
||||
> [!NOTE]
|
||||
> The default menu will be created automatically if the app does not set one.
|
||||
> It contains standard items such as `File`, `Edit`, `View`, `Window` and `Help`.
|
||||
> It contains standard items such as `File`, `Edit`, `View`, and `Window`.
|
||||
|
||||
#### `Menu.getApplicationMenu()`
|
||||
|
||||
@@ -70,7 +70,7 @@ for more information on macOS' native actions.
|
||||
|
||||
#### `Menu.buildFromTemplate(template)`
|
||||
|
||||
- `template` (MenuItemConstructorOptions | [MenuItem](menu-item.md))[]
|
||||
- `template` ([MenuItemConstructorOptions](menu-item.md#new-menuitemoptions) | [MenuItem](menu-item.md))[]
|
||||
|
||||
Returns [`Menu`](menu.md)
|
||||
|
||||
@@ -162,7 +162,7 @@ Emitted when a popup is closed either manually or with `menu.closePopup()`.
|
||||
|
||||
#### `menu.items`
|
||||
|
||||
A `MenuItem[]` array containing the menu's items.
|
||||
A [`MenuItem[]`](menu-item.md) array containing the menu's items.
|
||||
|
||||
Each `Menu` consists of multiple [`MenuItem`](menu-item.md) instances and each `MenuItem`
|
||||
can nest a `Menu` into its `submenu` property.
|
||||
|
||||
@@ -79,7 +79,7 @@ $ ../../electron/script/git-import-patches ../../electron/patches/node
|
||||
$ ../../electron/script/git-export-patches -o ../../electron/patches/node
|
||||
```
|
||||
|
||||
Note that `git-import-patches` will mark the commit that was `HEAD` when it was run as `refs/patches/upstream-head`. This lets you keep track of which commits are from Electron patches (those that come after `refs/patches/upstream-head`) and which commits are in upstream (those before `refs/patches/upstream-head`).
|
||||
Note that `git-import-patches` will mark the commit that was `HEAD` when it was run as `refs/patches/upstream-head` (and a checkout-specific `refs/patches/upstream-head-<hash>` so that gclient worktrees sharing a `.git/refs` directory don't clobber each other). This lets you keep track of which commits are from Electron patches (those that come after `refs/patches/upstream-head`) and which commits are in upstream (those before `refs/patches/upstream-head`).
|
||||
|
||||
#### Resolving conflicts
|
||||
|
||||
|
||||
@@ -41,8 +41,7 @@ Object.assign(app, {
|
||||
commandLine: {
|
||||
hasSwitch: (theSwitch: string) => commandLine.hasSwitch(String(theSwitch)),
|
||||
getSwitchValue: (theSwitch: string) => commandLine.getSwitchValue(String(theSwitch)),
|
||||
appendSwitch: (theSwitch: string, value?: string) =>
|
||||
commandLine.appendSwitch(String(theSwitch), typeof value === 'undefined' ? value : String(value)),
|
||||
appendSwitch: (theSwitch: string, value?: string) => commandLine.appendSwitch(String(theSwitch), typeof value === 'undefined' ? value : String(value)),
|
||||
appendArgument: (arg: string) => commandLine.appendArgument(String(arg)),
|
||||
removeSwitch: (theSwitch: string) => commandLine.removeSwitch(String(theSwitch))
|
||||
} as Electron.CommandLine
|
||||
@@ -51,10 +50,10 @@ Object.assign(app, {
|
||||
// we define this here because it'd be overly complicated to
|
||||
// do in native land
|
||||
Object.defineProperty(app, 'applicationMenu', {
|
||||
get() {
|
||||
get () {
|
||||
return Menu.getApplicationMenu();
|
||||
},
|
||||
set(menu: Electron.Menu | null) {
|
||||
set (menu: Electron.Menu | null) {
|
||||
return Menu.setApplicationMenu(menu);
|
||||
}
|
||||
});
|
||||
@@ -117,22 +116,17 @@ for (const name of events) {
|
||||
}
|
||||
|
||||
app._clientCertRequestPasswordHandler = null;
|
||||
app.setClientCertRequestPasswordHandler = function (
|
||||
handler: (params: Electron.ClientCertRequestParams) => Promise<string>
|
||||
) {
|
||||
app.setClientCertRequestPasswordHandler = function (handler: (params: Electron.ClientCertRequestParams) => Promise<string>) {
|
||||
app._clientCertRequestPasswordHandler = handler;
|
||||
};
|
||||
|
||||
app.on(
|
||||
'-client-certificate-request-password',
|
||||
async (event: Electron.Event<Electron.ClientCertRequestParams>, callback: (password: string) => void) => {
|
||||
event.preventDefault();
|
||||
const { hostname, tokenName, isRetry } = event;
|
||||
if (!app._clientCertRequestPasswordHandler) {
|
||||
callback('');
|
||||
return;
|
||||
}
|
||||
const password = await app._clientCertRequestPasswordHandler({ hostname, tokenName, isRetry });
|
||||
callback(password);
|
||||
app.on('-client-certificate-request-password', async (event: Electron.Event<Electron.ClientCertRequestParams>, callback: (password: string) => void) => {
|
||||
event.preventDefault();
|
||||
const { hostname, tokenName, isRetry } = event;
|
||||
if (!app._clientCertRequestPasswordHandler) {
|
||||
callback('');
|
||||
return;
|
||||
}
|
||||
);
|
||||
const password = await app._clientCertRequestPasswordHandler({ hostname, tokenName, isRetry });
|
||||
callback(password);
|
||||
});
|
||||
|
||||
@@ -135,7 +135,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
allowAnyVersion: boolean = false;
|
||||
|
||||
// Private: Validate that the URL points to an MSIX file (following redirects)
|
||||
private async validateMsixUrl(url: string): Promise<void> {
|
||||
private async validateMsixUrl (url: string): Promise<void> {
|
||||
try {
|
||||
// Make a HEAD request to follow redirects and get the final URL
|
||||
const response = await net.fetch(url, {
|
||||
@@ -153,9 +153,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
const hasMsixExtension = pathname.endsWith('.msix') || pathname.endsWith('.msixbundle');
|
||||
|
||||
if (!hasMsixExtension) {
|
||||
throw new Error(
|
||||
`Update URL does not point to an MSIX file. Expected .msix or .msixbundle extension, got final URL: ${finalUrl}`
|
||||
);
|
||||
throw new Error(`Update URL does not point to an MSIX file. Expected .msix or .msixbundle extension, got final URL: ${finalUrl}`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof TypeError) {
|
||||
@@ -166,7 +164,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
}
|
||||
|
||||
// Private: Check if URL is a direct MSIX file (following redirects)
|
||||
private async isDirectMsixUrl(url: string, emitError: boolean = false): Promise<boolean> {
|
||||
private async isDirectMsixUrl (url: string, emitError: boolean = false): Promise<boolean> {
|
||||
try {
|
||||
await this.validateMsixUrl(url);
|
||||
return true;
|
||||
@@ -180,12 +178,12 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
|
||||
// Supports both versioning (x.y.z) and Windows version format (x.y.z.a)
|
||||
// Returns: 1 if v1 > v2, -1 if v1 < v2, 0 if v1 === v2
|
||||
private compareVersions(v1: string, v2: string): number {
|
||||
const parts1 = v1.split('.').map((part) => {
|
||||
private compareVersions (v1: string, v2: string): number {
|
||||
const parts1 = v1.split('.').map(part => {
|
||||
const parsed = parseInt(part, 10);
|
||||
return isNaN(parsed) ? 0 : parsed;
|
||||
});
|
||||
const parts2 = v2.split('.').map((part) => {
|
||||
const parts2 = v2.split('.').map(part => {
|
||||
const parsed = parseInt(part, 10);
|
||||
return isNaN(parsed) ? 0 : parsed;
|
||||
});
|
||||
@@ -205,12 +203,9 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
|
||||
// Private: Parse the static releases array format
|
||||
// This is a static JSON file containing all releases
|
||||
private parseStaticReleasFile(
|
||||
json: any,
|
||||
currentVersion: string
|
||||
): { ok: boolean; available: boolean; url?: string; name?: string; notes?: string; pub_date?: string } {
|
||||
private parseStaticReleasFile (json: any, currentVersion: string): { ok: boolean; available: boolean; url?: string; name?: string; notes?: string; pub_date?: string } {
|
||||
if (!Array.isArray(json.releases) || !json.currentRelease || typeof json.currentRelease !== 'string') {
|
||||
this.emitError(new Error("Invalid releases format. Expected 'releases' array and 'currentRelease' string."));
|
||||
this.emitError(new Error('Invalid releases format. Expected \'releases\' array and \'currentRelease\' string.'));
|
||||
return { ok: false, available: false };
|
||||
}
|
||||
|
||||
@@ -239,18 +234,14 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
const releaseEntry = json.releases.find((r: any) => r.version === currentReleaseVersion);
|
||||
|
||||
if (!releaseEntry || !releaseEntry.updateTo) {
|
||||
this.emitError(
|
||||
new Error(`Release entry for version '${currentReleaseVersion}' not found or missing 'updateTo' property.`)
|
||||
);
|
||||
this.emitError(new Error(`Release entry for version '${currentReleaseVersion}' not found or missing 'updateTo' property.`));
|
||||
return { ok: false, available: false };
|
||||
}
|
||||
|
||||
const updateTo = releaseEntry.updateTo;
|
||||
|
||||
if (!updateTo.url) {
|
||||
this.emitError(
|
||||
new Error(`Invalid release entry. 'updateTo.url' is missing for version ${currentReleaseVersion}.`)
|
||||
);
|
||||
this.emitError(new Error(`Invalid release entry. 'updateTo.url' is missing for version ${currentReleaseVersion}.`));
|
||||
return { ok: false, available: false };
|
||||
}
|
||||
|
||||
@@ -264,22 +255,15 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
};
|
||||
}
|
||||
|
||||
private parseDynamicReleasFile(json: any): {
|
||||
ok: boolean;
|
||||
available: boolean;
|
||||
url?: string;
|
||||
name?: string;
|
||||
notes?: string;
|
||||
pub_date?: string;
|
||||
} {
|
||||
private parseDynamicReleasFile (json: any): { ok: boolean; available: boolean; url?: string; name?: string; notes?: string; pub_date?: string } {
|
||||
if (!json.url) {
|
||||
this.emitError(new Error("Invalid releases format. Expected 'url' string property."));
|
||||
this.emitError(new Error('Invalid releases format. Expected \'url\' string property.'));
|
||||
return { ok: false, available: false };
|
||||
}
|
||||
return { ok: true, available: true, url: json.url, name: json.name, notes: json.notes, pub_date: json.pub_date };
|
||||
}
|
||||
|
||||
private async fetchSquirrelJson(url: string) {
|
||||
private async fetchSquirrelJson (url: string) {
|
||||
const headers: Record<string, string> = {
|
||||
...this.updateHeaders,
|
||||
Accept: 'application/json' // Always set Accept header, overriding any user-provided Accept
|
||||
@@ -315,8 +299,8 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
private async getUpdateInfo(url: string): Promise<UpdateInfo> {
|
||||
if (url && (await this.isDirectMsixUrl(url))) {
|
||||
private async getUpdateInfo (url: string): Promise<UpdateInfo> {
|
||||
if (url && await this.isDirectMsixUrl(url)) {
|
||||
return { ok: true, available: true, updateUrl: url, releaseDate: new Date() };
|
||||
} else {
|
||||
const updateJson = await this.fetchSquirrelJson(url);
|
||||
@@ -337,7 +321,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
const releaseName = updateJson.name ?? '';
|
||||
releaseDate = releaseDate ?? new Date();
|
||||
|
||||
if (!(await this.isDirectMsixUrl(updateUrl, true))) {
|
||||
if (!await this.isDirectMsixUrl(updateUrl, true)) {
|
||||
return { ok: false };
|
||||
} else {
|
||||
return {
|
||||
@@ -353,11 +337,11 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
getFeedURL() {
|
||||
getFeedURL () {
|
||||
return this.updateURL ?? '';
|
||||
}
|
||||
|
||||
setFeedURL(options: { url: string; headers?: Record<string, string>; allowAnyVersion?: boolean } | string) {
|
||||
setFeedURL (options: { url: string; headers?: Record<string, string>; allowAnyVersion?: boolean } | string) {
|
||||
let updateURL: string;
|
||||
let headers: Record<string, string> | undefined;
|
||||
let allowAnyVersion: boolean | undefined;
|
||||
@@ -367,23 +351,23 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
headers = options.headers;
|
||||
allowAnyVersion = options.allowAnyVersion;
|
||||
} else {
|
||||
throw new TypeError("Expected options object to contain a 'url' string property in setFeedUrl call");
|
||||
throw new TypeError('Expected options object to contain a \'url\' string property in setFeedUrl call');
|
||||
}
|
||||
} else if (typeof options === 'string') {
|
||||
updateURL = options;
|
||||
} else {
|
||||
throw new TypeError("Expected an options object with a 'url' property to be provided");
|
||||
throw new TypeError('Expected an options object with a \'url\' property to be provided');
|
||||
}
|
||||
this.updateURL = updateURL;
|
||||
this.updateHeaders = headers ?? null;
|
||||
this.allowAnyVersion = allowAnyVersion ?? false;
|
||||
}
|
||||
|
||||
getPackageInfo(): MSIXPackageInfo {
|
||||
getPackageInfo (): MSIXPackageInfo {
|
||||
return msixUpdate.getPackageInfo() as MSIXPackageInfo;
|
||||
}
|
||||
|
||||
async checkForUpdates() {
|
||||
async checkForUpdates () {
|
||||
const url = this.updateURL;
|
||||
if (!url) {
|
||||
return this.emitError(new Error('Update URL is not set'));
|
||||
@@ -398,11 +382,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
// If appInstallerUri is set, Windows App Installer manages updates automatically
|
||||
// Prevent updates here to avoid conflicts
|
||||
if (packageInfo.appInstallerUri) {
|
||||
return this.emitError(
|
||||
new Error(
|
||||
'Auto-updates are managed by Windows App Installer. Updates are not allowed when installed via Application Manifest.'
|
||||
)
|
||||
);
|
||||
return this.emitError(new Error('Auto-updates are managed by Windows App Installer. Updates are not allowed when installed via Application Manifest.'));
|
||||
}
|
||||
|
||||
this.emit('checking-for-update');
|
||||
@@ -425,26 +405,18 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
forceUpdateFromAnyVersion: this.allowAnyVersion
|
||||
} as UpdateMsixOptions);
|
||||
|
||||
this.emit(
|
||||
'update-downloaded',
|
||||
{},
|
||||
msixUrlInfo.releaseNotes,
|
||||
msixUrlInfo.releaseName,
|
||||
msixUrlInfo.releaseDate,
|
||||
msixUrlInfo.updateUrl,
|
||||
() => {
|
||||
this.quitAndInstall();
|
||||
}
|
||||
);
|
||||
this.emit('update-downloaded', {}, msixUrlInfo.releaseNotes, msixUrlInfo.releaseName, msixUrlInfo.releaseDate, msixUrlInfo.updateUrl, () => {
|
||||
this.quitAndInstall();
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.emitError(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
async quitAndInstall() {
|
||||
async quitAndInstall () {
|
||||
if (!this.updateAvailable) {
|
||||
this.emitError(new Error("No update available, can't quit and install"));
|
||||
this.emitError(new Error('No update available, can\'t quit and install'));
|
||||
app.quit();
|
||||
return;
|
||||
}
|
||||
@@ -469,7 +441,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
|
||||
// Private: Emit both error object and message, this is to keep compatibility
|
||||
// with Old APIs.
|
||||
emitError(error: Error) {
|
||||
emitError (error: Error) {
|
||||
this.emit('error', error, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,40 +8,40 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
updateAvailable: boolean = false;
|
||||
updateURL: string | null = null;
|
||||
|
||||
quitAndInstall() {
|
||||
quitAndInstall () {
|
||||
if (!this.updateAvailable) {
|
||||
return this.emitError(new Error("No update available, can't quit and install"));
|
||||
return this.emitError(new Error('No update available, can\'t quit and install'));
|
||||
}
|
||||
squirrelUpdate.processStart();
|
||||
app.quit();
|
||||
}
|
||||
|
||||
getFeedURL() {
|
||||
getFeedURL () {
|
||||
return this.updateURL ?? '';
|
||||
}
|
||||
|
||||
getPackageInfo() {
|
||||
getPackageInfo () {
|
||||
// Squirrel-based Windows apps don't have MSIX package information
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setFeedURL(options: { url: string } | string) {
|
||||
setFeedURL (options: { url: string } | string) {
|
||||
let updateURL: string;
|
||||
if (typeof options === 'object') {
|
||||
if (typeof options.url === 'string') {
|
||||
updateURL = options.url;
|
||||
} else {
|
||||
throw new TypeError("Expected options object to contain a 'url' string property in setFeedUrl call");
|
||||
throw new TypeError('Expected options object to contain a \'url\' string property in setFeedUrl call');
|
||||
}
|
||||
} else if (typeof options === 'string') {
|
||||
updateURL = options;
|
||||
} else {
|
||||
throw new TypeError("Expected an options object with a 'url' property to be provided");
|
||||
throw new TypeError('Expected an options object with a \'url\' property to be provided');
|
||||
}
|
||||
this.updateURL = updateURL;
|
||||
}
|
||||
|
||||
async checkForUpdates() {
|
||||
async checkForUpdates () {
|
||||
const url = this.updateURL;
|
||||
if (!url) {
|
||||
return this.emitError(new Error('Update URL is not set'));
|
||||
@@ -72,7 +72,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
|
||||
// Private: Emit both error object and message, this is to keep compatibility
|
||||
// with Old APIs.
|
||||
emitError(error: Error) {
|
||||
emitError (error: Error) {
|
||||
this.emit('error', error, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const { updateMsix, registerPackage, registerRestartOnUpdate, getPackageInfo } = process._linkedBinding(
|
||||
'electron_browser_msix_updater'
|
||||
);
|
||||
const { updateMsix, registerPackage, registerRestartOnUpdate, getPackageInfo } =
|
||||
process._linkedBinding('electron_browser_msix_updater');
|
||||
|
||||
export { updateMsix, registerPackage, registerRestartOnUpdate, getPackageInfo };
|
||||
|
||||
@@ -34,12 +34,8 @@ const spawnUpdate = async function (args: string[], options: { detached: boolean
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
spawnedProcess.stdout.on('data', (data) => {
|
||||
stdout += data;
|
||||
});
|
||||
spawnedProcess.stderr.on('data', (data) => {
|
||||
stderr += data;
|
||||
});
|
||||
spawnedProcess.stdout.on('data', (data) => { stdout += data; });
|
||||
spawnedProcess.stderr.on('data', (data) => { stderr += data; });
|
||||
|
||||
spawnedProcess.on('error', (error) => {
|
||||
spawnedProcess = undefined;
|
||||
@@ -63,12 +59,12 @@ const spawnUpdate = async function (args: string[], options: { detached: boolean
|
||||
};
|
||||
|
||||
// Start an instance of the installed app.
|
||||
export function processStart() {
|
||||
export function processStart () {
|
||||
spawnUpdate(['--processStartAndWait', exeName], { detached: true });
|
||||
}
|
||||
|
||||
// Download the releases specified by the URL and write new results to stdout.
|
||||
export async function checkForUpdate(updateURL: string): Promise<any> {
|
||||
export async function checkForUpdate (updateURL: string): Promise<any> {
|
||||
const stdout = await spawnUpdate(['--checkForUpdate', updateURL], { detached: false });
|
||||
try {
|
||||
// Last line of output is the JSON details about the releases
|
||||
@@ -80,12 +76,12 @@ export async function checkForUpdate(updateURL: string): Promise<any> {
|
||||
}
|
||||
|
||||
// Update the application to the latest remote version specified by URL.
|
||||
export async function update(updateURL: string): Promise<void> {
|
||||
export async function update (updateURL: string): Promise<void> {
|
||||
await spawnUpdate(['--update', updateURL], { detached: false });
|
||||
}
|
||||
|
||||
// Is the Update.exe installed with the current application?
|
||||
export function supported() {
|
||||
export function supported () {
|
||||
try {
|
||||
fs.accessSync(updateExe, fs.constants.R_OK);
|
||||
return true;
|
||||
|
||||
@@ -25,156 +25,88 @@ BaseWindow.prototype.setTouchBar = function (touchBar) {
|
||||
// Properties
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'autoHideMenuBar', {
|
||||
get: function () {
|
||||
return this.isMenuBarAutoHide();
|
||||
},
|
||||
set: function (autoHide) {
|
||||
this.setAutoHideMenuBar(autoHide);
|
||||
}
|
||||
get: function () { return this.isMenuBarAutoHide(); },
|
||||
set: function (autoHide) { this.setAutoHideMenuBar(autoHide); }
|
||||
});
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'visibleOnAllWorkspaces', {
|
||||
get: function () {
|
||||
return this.isVisibleOnAllWorkspaces();
|
||||
},
|
||||
set: function (visible) {
|
||||
this.setVisibleOnAllWorkspaces(visible);
|
||||
}
|
||||
get: function () { return this.isVisibleOnAllWorkspaces(); },
|
||||
set: function (visible) { this.setVisibleOnAllWorkspaces(visible); }
|
||||
});
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'fullScreen', {
|
||||
get: function () {
|
||||
return this.isFullScreen();
|
||||
},
|
||||
set: function (full) {
|
||||
this.setFullScreen(full);
|
||||
}
|
||||
get: function () { return this.isFullScreen(); },
|
||||
set: function (full) { this.setFullScreen(full); }
|
||||
});
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'simpleFullScreen', {
|
||||
get: function () {
|
||||
return this.isSimpleFullScreen();
|
||||
},
|
||||
set: function (simple) {
|
||||
this.setSimpleFullScreen(simple);
|
||||
}
|
||||
get: function () { return this.isSimpleFullScreen(); },
|
||||
set: function (simple) { this.setSimpleFullScreen(simple); }
|
||||
});
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'focusable', {
|
||||
get: function () {
|
||||
return this.isFocusable();
|
||||
},
|
||||
set: function (focusable) {
|
||||
this.setFocusable(focusable);
|
||||
}
|
||||
get: function () { return this.isFocusable(); },
|
||||
set: function (focusable) { this.setFocusable(focusable); }
|
||||
});
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'kiosk', {
|
||||
get: function () {
|
||||
return this.isKiosk();
|
||||
},
|
||||
set: function (kiosk) {
|
||||
this.setKiosk(kiosk);
|
||||
}
|
||||
get: function () { return this.isKiosk(); },
|
||||
set: function (kiosk) { this.setKiosk(kiosk); }
|
||||
});
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'documentEdited', {
|
||||
get: function () {
|
||||
return this.isDocumentEdited();
|
||||
},
|
||||
set: function (edited) {
|
||||
this.setDocumentEdited(edited);
|
||||
}
|
||||
get: function () { return this.isDocumentEdited(); },
|
||||
set: function (edited) { this.setDocumentEdited(edited); }
|
||||
});
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'shadow', {
|
||||
get: function () {
|
||||
return this.hasShadow();
|
||||
},
|
||||
set: function (shadow) {
|
||||
this.setHasShadow(shadow);
|
||||
}
|
||||
get: function () { return this.hasShadow(); },
|
||||
set: function (shadow) { this.setHasShadow(shadow); }
|
||||
});
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'representedFilename', {
|
||||
get: function () {
|
||||
return this.getRepresentedFilename();
|
||||
},
|
||||
set: function (filename) {
|
||||
this.setRepresentedFilename(filename);
|
||||
}
|
||||
get: function () { return this.getRepresentedFilename(); },
|
||||
set: function (filename) { this.setRepresentedFilename(filename); }
|
||||
});
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'minimizable', {
|
||||
get: function () {
|
||||
return this.isMinimizable();
|
||||
},
|
||||
set: function (min) {
|
||||
this.setMinimizable(min);
|
||||
}
|
||||
get: function () { return this.isMinimizable(); },
|
||||
set: function (min) { this.setMinimizable(min); }
|
||||
});
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'title', {
|
||||
get: function () {
|
||||
return this.getTitle();
|
||||
},
|
||||
set: function (title) {
|
||||
this.setTitle(title);
|
||||
}
|
||||
get: function () { return this.getTitle(); },
|
||||
set: function (title) { this.setTitle(title); }
|
||||
});
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'maximizable', {
|
||||
get: function () {
|
||||
return this.isMaximizable();
|
||||
},
|
||||
set: function (max) {
|
||||
this.setMaximizable(max);
|
||||
}
|
||||
get: function () { return this.isMaximizable(); },
|
||||
set: function (max) { this.setMaximizable(max); }
|
||||
});
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'resizable', {
|
||||
get: function () {
|
||||
return this.isResizable();
|
||||
},
|
||||
set: function (res) {
|
||||
this.setResizable(res);
|
||||
}
|
||||
get: function () { return this.isResizable(); },
|
||||
set: function (res) { this.setResizable(res); }
|
||||
});
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'menuBarVisible', {
|
||||
get: function () {
|
||||
return this.isMenuBarVisible();
|
||||
},
|
||||
set: function (visible) {
|
||||
this.setMenuBarVisibility(visible);
|
||||
}
|
||||
get: function () { return this.isMenuBarVisible(); },
|
||||
set: function (visible) { this.setMenuBarVisibility(visible); }
|
||||
});
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'fullScreenable', {
|
||||
get: function () {
|
||||
return this.isFullScreenable();
|
||||
},
|
||||
set: function (full) {
|
||||
this.setFullScreenable(full);
|
||||
}
|
||||
get: function () { return this.isFullScreenable(); },
|
||||
set: function (full) { this.setFullScreenable(full); }
|
||||
});
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'closable', {
|
||||
get: function () {
|
||||
return this.isClosable();
|
||||
},
|
||||
set: function (close) {
|
||||
this.setClosable(close);
|
||||
}
|
||||
get: function () { return this.isClosable(); },
|
||||
set: function (close) { this.setClosable(close); }
|
||||
});
|
||||
|
||||
Object.defineProperty(BaseWindow.prototype, 'movable', {
|
||||
get: function () {
|
||||
return this.isMovable();
|
||||
},
|
||||
set: function (move) {
|
||||
this.setMovable(move);
|
||||
}
|
||||
get: function () { return this.isMovable(); },
|
||||
set: function (move) { this.setMovable(move); }
|
||||
});
|
||||
|
||||
BaseWindow.getFocusedWindow = () => {
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
BrowserWindow,
|
||||
AutoResizeOptions,
|
||||
Rectangle,
|
||||
WebContentsView,
|
||||
WebPreferences,
|
||||
WebContents
|
||||
} from 'electron/main';
|
||||
import { BrowserWindow, AutoResizeOptions, Rectangle, WebContentsView, WebPreferences, WebContents } from 'electron/main';
|
||||
|
||||
const v8Util = process._linkedBinding('electron_common_v8_util');
|
||||
|
||||
@@ -17,10 +10,10 @@ export default class BrowserView {
|
||||
|
||||
// AutoResize state
|
||||
#resizeListener: ((...args: any[]) => void) | null = null;
|
||||
#lastWindowSize: { width: number; height: number } = { width: 0, height: 0 };
|
||||
#lastWindowSize: {width: number, height: number} = { width: 0, height: 0 };
|
||||
#autoResizeFlags: AutoResizeOptions = {};
|
||||
|
||||
constructor(options: { webPreferences: WebPreferences; webContents?: WebContents } = { webPreferences: {} }) {
|
||||
constructor (options: {webPreferences: WebPreferences, webContents?: WebContents} = { webPreferences: {} }) {
|
||||
const { webPreferences = {}, webContents } = options;
|
||||
if (webContents) {
|
||||
v8Util.setHiddenValue(webPreferences, 'webContents', webContents);
|
||||
@@ -32,21 +25,21 @@ export default class BrowserView {
|
||||
this.#webContentsView.webContents.once('destroyed', this.#destroyListener);
|
||||
}
|
||||
|
||||
get webContents() {
|
||||
get webContents () {
|
||||
return this.#webContentsView.webContents;
|
||||
}
|
||||
|
||||
setBounds(bounds: Rectangle) {
|
||||
setBounds (bounds: Rectangle) {
|
||||
this.#webContentsView.setBounds(bounds);
|
||||
this.#autoHorizontalProportion = null;
|
||||
this.#autoVerticalProportion = null;
|
||||
}
|
||||
|
||||
getBounds() {
|
||||
getBounds () {
|
||||
return this.#webContentsView.getBounds();
|
||||
}
|
||||
|
||||
setAutoResize(options: AutoResizeOptions) {
|
||||
setAutoResize (options: AutoResizeOptions) {
|
||||
if (options == null || typeof options !== 'object') {
|
||||
throw new Error('Invalid auto resize options');
|
||||
}
|
||||
@@ -62,19 +55,19 @@ export default class BrowserView {
|
||||
this.#autoVerticalProportion = null;
|
||||
}
|
||||
|
||||
setBackgroundColor(color: string) {
|
||||
setBackgroundColor (color: string) {
|
||||
this.#webContentsView.setBackgroundColor(color);
|
||||
}
|
||||
|
||||
// Internal methods
|
||||
get ownerWindow(): BrowserWindow | null {
|
||||
get ownerWindow (): BrowserWindow | null {
|
||||
return this.#ownerWindow;
|
||||
}
|
||||
|
||||
// We can't rely solely on the webContents' owner window because
|
||||
// a webContents can be closed by the user while the BrowserView
|
||||
// remains alive and attached to a BrowserWindow.
|
||||
set ownerWindow(w: BrowserWindow | null) {
|
||||
set ownerWindow (w: BrowserWindow | null) {
|
||||
this.#removeResizeListener();
|
||||
|
||||
if (this.webContents && !this.webContents.isDestroyed()) {
|
||||
@@ -84,7 +77,7 @@ export default class BrowserView {
|
||||
this.#ownerWindow = w;
|
||||
if (w) {
|
||||
this.#lastWindowSize = w.getBounds();
|
||||
w.on('resize', (this.#resizeListener = this.#autoResize.bind(this)));
|
||||
w.on('resize', this.#resizeListener = this.#autoResize.bind(this));
|
||||
w.on('closed', () => {
|
||||
this.#removeResizeListener();
|
||||
this.#ownerWindow = null;
|
||||
@@ -93,25 +86,25 @@ export default class BrowserView {
|
||||
}
|
||||
}
|
||||
|
||||
#onDestroy() {
|
||||
#onDestroy () {
|
||||
// Ensure that if #webContentsView's webContents is destroyed,
|
||||
// the WebContentsView is removed from the view hierarchy.
|
||||
this.#ownerWindow?.contentView.removeChildView(this.webContentsView);
|
||||
}
|
||||
|
||||
#removeResizeListener() {
|
||||
#removeResizeListener () {
|
||||
if (this.#ownerWindow && this.#resizeListener) {
|
||||
this.#ownerWindow.off('resize', this.#resizeListener);
|
||||
this.#resizeListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
#autoHorizontalProportion: { width: number; left: number } | null = null;
|
||||
#autoVerticalProportion: { height: number; top: number } | null = null;
|
||||
#autoResize() {
|
||||
#autoHorizontalProportion: {width: number, left: number} | null = null;
|
||||
#autoVerticalProportion: {height: number, top: number} | null = null;
|
||||
#autoResize () {
|
||||
if (!this.ownerWindow) {
|
||||
throw new Error('Electron bug: #autoResize called without owner window');
|
||||
}
|
||||
};
|
||||
|
||||
if (this.#autoResizeFlags.horizontal && this.#autoHorizontalProportion == null) {
|
||||
const viewBounds = this.#webContentsView.getBounds();
|
||||
@@ -165,7 +158,7 @@ export default class BrowserView {
|
||||
};
|
||||
}
|
||||
|
||||
get webContentsView() {
|
||||
get webContentsView () {
|
||||
return this.#webContentsView;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,14 +40,10 @@ BrowserWindow.prototype._init = function (this: BWT) {
|
||||
let unresponsiveEvent: NodeJS.Timeout | null = null;
|
||||
const emitUnresponsiveEvent = () => {
|
||||
unresponsiveEvent = null;
|
||||
if (!this.isDestroyed() && this.isEnabled()) {
|
||||
this.emit('unresponsive');
|
||||
}
|
||||
if (!this.isDestroyed() && this.isEnabled()) { this.emit('unresponsive'); }
|
||||
};
|
||||
this.webContents.on('unresponsive', () => {
|
||||
if (!unresponsiveEvent) {
|
||||
unresponsiveEvent = setTimeout(emitUnresponsiveEvent, 50);
|
||||
}
|
||||
if (!unresponsiveEvent) { unresponsiveEvent = setTimeout(emitUnresponsiveEvent, 50); }
|
||||
});
|
||||
this.webContents.on('responsive', () => {
|
||||
if (unresponsiveEvent) {
|
||||
@@ -87,16 +83,16 @@ BrowserWindow.prototype._init = function (this: BWT) {
|
||||
this._browserViews = [];
|
||||
|
||||
this.on('closed', () => {
|
||||
this._browserViews.forEach((b) => b.webContents?.close({ waitForBeforeUnload: true }));
|
||||
this._browserViews.forEach(b => b.webContents?.close({ waitForBeforeUnload: true }));
|
||||
});
|
||||
|
||||
// Notify the creation of the window.
|
||||
app.emit('browser-window-created', { preventDefault() {} }, this);
|
||||
app.emit('browser-window-created', { preventDefault () {} }, this);
|
||||
|
||||
Object.defineProperty(this, 'devToolsWebContents', {
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
get() {
|
||||
get () {
|
||||
return this.webContents.devToolsWebContents;
|
||||
}
|
||||
});
|
||||
@@ -108,7 +104,7 @@ const isBrowserWindow = (win: any) => {
|
||||
|
||||
BrowserWindow.fromId = (id: number) => {
|
||||
const win = BaseWindow.fromId(id);
|
||||
return isBrowserWindow(win) ? (win as any as BWT) : null;
|
||||
return isBrowserWindow(win) ? win as any as BWT : null;
|
||||
};
|
||||
|
||||
BrowserWindow.getAllWindows = () => {
|
||||
@@ -218,12 +214,10 @@ BrowserWindow.prototype.addBrowserView = function (browserView: BrowserView) {
|
||||
};
|
||||
|
||||
BrowserWindow.prototype.setBrowserView = function (browserView: BrowserView) {
|
||||
this._browserViews.forEach((bv) => {
|
||||
this._browserViews.forEach(bv => {
|
||||
this.removeBrowserView(bv);
|
||||
});
|
||||
if (browserView) {
|
||||
this.addBrowserView(browserView);
|
||||
}
|
||||
if (browserView) { this.addBrowserView(browserView); }
|
||||
};
|
||||
|
||||
BrowserWindow.prototype.removeBrowserView = function (browserView: BrowserView) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { app } from 'electron/main';
|
||||
const binding = process._linkedBinding('electron_browser_crash_reporter');
|
||||
|
||||
class CrashReporter implements Electron.CrashReporter {
|
||||
start(options: Electron.CrashReporterStartOptions) {
|
||||
start (options: Electron.CrashReporterStartOptions) {
|
||||
const {
|
||||
productName = app.name,
|
||||
companyName,
|
||||
@@ -21,9 +21,7 @@ class CrashReporter implements Electron.CrashReporter {
|
||||
if (uploadToServer && !submitURL) throw new Error('submitURL must be specified when uploadToServer is true');
|
||||
|
||||
if (!compress && uploadToServer) {
|
||||
deprecate.log(
|
||||
'Sending uncompressed crash reports is deprecated and will be removed in a future version of Electron. Set { compress: true } to opt-in to the new behavior. Crash reports will be uploaded gzipped, which most crash reporting servers support.'
|
||||
);
|
||||
deprecate.log('Sending uncompressed crash reports is deprecated and will be removed in a future version of Electron. Set { compress: true } to opt-in to the new behavior. Crash reports will be uploaded gzipped, which most crash reporting servers support.');
|
||||
}
|
||||
|
||||
const appVersion = app.getVersion();
|
||||
@@ -36,33 +34,26 @@ class CrashReporter implements Electron.CrashReporter {
|
||||
...globalExtra
|
||||
};
|
||||
|
||||
binding.start(
|
||||
submitURL,
|
||||
uploadToServer,
|
||||
ignoreSystemCrashHandler,
|
||||
rateLimit,
|
||||
compress,
|
||||
globalExtraAmended,
|
||||
extra,
|
||||
false
|
||||
);
|
||||
binding.start(submitURL, uploadToServer,
|
||||
ignoreSystemCrashHandler, rateLimit, compress, globalExtraAmended, extra, false);
|
||||
}
|
||||
|
||||
getLastCrashReport() {
|
||||
const reports = this.getUploadedReports().sort((a, b) => {
|
||||
const ats = a && a.date ? new Date(a.date).getTime() : 0;
|
||||
const bts = b && b.date ? new Date(b.date).getTime() : 0;
|
||||
return bts - ats;
|
||||
});
|
||||
getLastCrashReport () {
|
||||
const reports = this.getUploadedReports()
|
||||
.sort((a, b) => {
|
||||
const ats = (a && a.date) ? new Date(a.date).getTime() : 0;
|
||||
const bts = (b && b.date) ? new Date(b.date).getTime() : 0;
|
||||
return bts - ats;
|
||||
});
|
||||
|
||||
return reports.length > 0 ? reports[0] : null;
|
||||
return (reports.length > 0) ? reports[0] : null;
|
||||
}
|
||||
|
||||
getUploadedReports(): Electron.CrashReport[] {
|
||||
getUploadedReports (): Electron.CrashReport[] {
|
||||
return binding.getUploadedReports();
|
||||
}
|
||||
|
||||
getUploadToServer() {
|
||||
getUploadToServer () {
|
||||
if (process.type === 'browser') {
|
||||
return binding.getUploadToServer();
|
||||
} else {
|
||||
@@ -70,7 +61,7 @@ class CrashReporter implements Electron.CrashReporter {
|
||||
}
|
||||
}
|
||||
|
||||
setUploadToServer(uploadToServer: boolean) {
|
||||
setUploadToServer (uploadToServer: boolean) {
|
||||
if (process.type === 'browser') {
|
||||
return binding.setUploadToServer(uploadToServer);
|
||||
} else {
|
||||
@@ -78,15 +69,15 @@ class CrashReporter implements Electron.CrashReporter {
|
||||
}
|
||||
}
|
||||
|
||||
addExtraParameter(key: string, value: string) {
|
||||
addExtraParameter (key: string, value: string) {
|
||||
binding.addExtraParameter(key, value);
|
||||
}
|
||||
|
||||
removeExtraParameter(key: string) {
|
||||
removeExtraParameter (key: string) {
|
||||
binding.removeExtraParameter(key);
|
||||
}
|
||||
|
||||
getParameters() {
|
||||
getParameters () {
|
||||
return binding.getParameters();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { BrowserWindow } from 'electron/main';
|
||||
|
||||
const { createDesktopCapturer, isDisplayMediaSystemPickerAvailable } = process._linkedBinding(
|
||||
'electron_browser_desktop_capturer'
|
||||
);
|
||||
const { createDesktopCapturer, isDisplayMediaSystemPickerAvailable } = process._linkedBinding('electron_browser_desktop_capturer');
|
||||
|
||||
const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) =>
|
||||
JSON.stringify(a) === JSON.stringify(b);
|
||||
const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) => JSON.stringify(a) === JSON.stringify(b);
|
||||
|
||||
let currentlyRunning: {
|
||||
options: ElectronInternal.GetSourcesOptions;
|
||||
@@ -13,13 +10,13 @@ let currentlyRunning: {
|
||||
}[] = [];
|
||||
|
||||
// |options.types| can't be empty and must be an array
|
||||
function isValid(options: Electron.SourcesOptions) {
|
||||
function isValid (options: Electron.SourcesOptions) {
|
||||
return Array.isArray(options?.types);
|
||||
}
|
||||
|
||||
export { isDisplayMediaSystemPickerAvailable };
|
||||
|
||||
export async function getSources(args: Electron.SourcesOptions) {
|
||||
export async function getSources (args: Electron.SourcesOptions) {
|
||||
if (!isValid(args)) throw new Error('Invalid options');
|
||||
|
||||
const resizableValues = new Map();
|
||||
@@ -67,11 +64,11 @@ export async function getSources(args: Electron.SourcesOptions) {
|
||||
if (resizableValues.has(win.id)) {
|
||||
win.resizable = resizableValues.get(win.id);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
// Remove from currentlyRunning once we resolve or reject
|
||||
currentlyRunning = currentlyRunning.filter((running) => running.options !== options);
|
||||
currentlyRunning = currentlyRunning.filter(running => running.options !== options);
|
||||
};
|
||||
|
||||
capturer._onerror = (error: string) => {
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
import { app, BaseWindow } from 'electron/main';
|
||||
import type {
|
||||
OpenDialogOptions,
|
||||
OpenDialogReturnValue,
|
||||
MessageBoxOptions,
|
||||
SaveDialogOptions,
|
||||
SaveDialogReturnValue,
|
||||
MessageBoxReturnValue,
|
||||
CertificateTrustDialogOptions
|
||||
} from 'electron/main';
|
||||
import type { OpenDialogOptions, OpenDialogReturnValue, MessageBoxOptions, SaveDialogOptions, SaveDialogReturnValue, MessageBoxReturnValue, CertificateTrustDialogOptions } from 'electron/main';
|
||||
|
||||
const dialogBinding = process._linkedBinding('electron_browser_dialog');
|
||||
|
||||
@@ -68,9 +60,7 @@ const checkAppInitialized = function () {
|
||||
const setupOpenDialogProperties = (properties: (keyof typeof OpenFileDialogProperties)[]): number => {
|
||||
let dialogProperties = 0;
|
||||
for (const property of properties) {
|
||||
if (Object.hasOwn(OpenFileDialogProperties, property)) {
|
||||
dialogProperties |= OpenFileDialogProperties[property];
|
||||
}
|
||||
if (Object.hasOwn(OpenFileDialogProperties, property)) { dialogProperties |= OpenFileDialogProperties[property]; }
|
||||
}
|
||||
return dialogProperties;
|
||||
};
|
||||
@@ -78,9 +68,7 @@ const setupOpenDialogProperties = (properties: (keyof typeof OpenFileDialogPrope
|
||||
const setupSaveDialogProperties = (properties: (keyof typeof SaveFileDialogProperties)[]): number => {
|
||||
let dialogProperties = 0;
|
||||
for (const property of properties) {
|
||||
if (Object.hasOwn(SaveFileDialogProperties, property)) {
|
||||
dialogProperties |= SaveFileDialogProperties[property];
|
||||
}
|
||||
if (Object.hasOwn(SaveFileDialogProperties, property)) { dialogProperties |= SaveFileDialogProperties[property]; }
|
||||
}
|
||||
return dialogProperties;
|
||||
};
|
||||
@@ -162,7 +150,7 @@ const openDialog = (sync: boolean, window: BaseWindow | null, options?: OpenDial
|
||||
properties: setupOpenDialogProperties(properties)
|
||||
};
|
||||
|
||||
return sync ? dialogBinding.showOpenDialogSync(settings) : dialogBinding.showOpenDialog(settings);
|
||||
return (sync) ? dialogBinding.showOpenDialogSync(settings) : dialogBinding.showOpenDialog(settings);
|
||||
};
|
||||
|
||||
const messageBox = (sync: boolean, window: BaseWindow | null, options?: MessageBoxOptions) => {
|
||||
@@ -206,7 +194,7 @@ const messageBox = (sync: boolean, window: BaseWindow | null, options?: MessageB
|
||||
// Choose a default button to get selected when dialog is cancelled.
|
||||
if (cancelId == null) {
|
||||
// If the defaultId is set to 0, ensure the cancel button is a different index (1)
|
||||
cancelId = defaultId === 0 && buttons.length > 1 ? 1 : 0;
|
||||
cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0;
|
||||
for (const [i, button] of buttons.entries()) {
|
||||
const text = button.toLowerCase();
|
||||
if (text === 'cancel' || text === 'no') {
|
||||
@@ -222,9 +210,7 @@ const messageBox = (sync: boolean, window: BaseWindow | null, options?: MessageB
|
||||
// Generate an ID used for closing the message box.
|
||||
id = getNextId();
|
||||
// Close the message box when signal is aborted.
|
||||
if (signal.aborted) {
|
||||
return Promise.resolve({ cancelId, checkboxChecked });
|
||||
}
|
||||
if (signal.aborted) { return Promise.resolve({ cancelId, checkboxChecked }); }
|
||||
signal.addEventListener('abort', () => dialogBinding._closeMessageBox(id));
|
||||
}
|
||||
|
||||
@@ -254,80 +240,59 @@ const messageBox = (sync: boolean, window: BaseWindow | null, options?: MessageB
|
||||
|
||||
export function showOpenDialog(window: BaseWindow, options: OpenDialogOptions): OpenDialogReturnValue;
|
||||
export function showOpenDialog(options: OpenDialogOptions): OpenDialogReturnValue;
|
||||
export function showOpenDialog(
|
||||
windowOrOptions: BaseWindow | OpenDialogOptions,
|
||||
maybeOptions?: OpenDialogOptions
|
||||
): OpenDialogReturnValue {
|
||||
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
|
||||
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
|
||||
export function showOpenDialog (windowOrOptions: BaseWindow | OpenDialogOptions, maybeOptions?: OpenDialogOptions): OpenDialogReturnValue {
|
||||
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
|
||||
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
|
||||
return openDialog(false, window, options);
|
||||
}
|
||||
|
||||
export function showOpenDialogSync(window: BaseWindow, options: OpenDialogOptions): OpenDialogReturnValue;
|
||||
export function showOpenDialogSync(options: OpenDialogOptions): OpenDialogReturnValue;
|
||||
export function showOpenDialogSync(
|
||||
windowOrOptions: BaseWindow | OpenDialogOptions,
|
||||
maybeOptions?: OpenDialogOptions
|
||||
): OpenDialogReturnValue {
|
||||
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
|
||||
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
|
||||
export function showOpenDialogSync (windowOrOptions: BaseWindow | OpenDialogOptions, maybeOptions?: OpenDialogOptions): OpenDialogReturnValue {
|
||||
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
|
||||
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
|
||||
return openDialog(true, window, options);
|
||||
}
|
||||
|
||||
export function showSaveDialog(window: BaseWindow, options: SaveDialogOptions): SaveDialogReturnValue;
|
||||
export function showSaveDialog(options: SaveDialogOptions): SaveDialogReturnValue;
|
||||
export function showSaveDialog(
|
||||
windowOrOptions: BaseWindow | SaveDialogOptions,
|
||||
maybeOptions?: SaveDialogOptions
|
||||
): SaveDialogReturnValue {
|
||||
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
|
||||
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
|
||||
export function showSaveDialog (windowOrOptions: BaseWindow | SaveDialogOptions, maybeOptions?: SaveDialogOptions): SaveDialogReturnValue {
|
||||
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
|
||||
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
|
||||
return saveDialog(false, window, options);
|
||||
}
|
||||
|
||||
export function showSaveDialogSync(window: BaseWindow, options: SaveDialogOptions): SaveDialogReturnValue;
|
||||
export function showSaveDialogSync(options: SaveDialogOptions): SaveDialogReturnValue;
|
||||
export function showSaveDialogSync(
|
||||
windowOrOptions: BaseWindow | SaveDialogOptions,
|
||||
maybeOptions?: SaveDialogOptions
|
||||
): SaveDialogReturnValue {
|
||||
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
|
||||
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
|
||||
export function showSaveDialogSync (windowOrOptions: BaseWindow | SaveDialogOptions, maybeOptions?: SaveDialogOptions): SaveDialogReturnValue {
|
||||
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
|
||||
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
|
||||
return saveDialog(true, window, options);
|
||||
}
|
||||
|
||||
export function showMessageBox(window: BaseWindow, options: MessageBoxOptions): MessageBoxReturnValue;
|
||||
export function showMessageBox(options: MessageBoxOptions): MessageBoxReturnValue;
|
||||
export function showMessageBox(
|
||||
windowOrOptions: BaseWindow | MessageBoxOptions,
|
||||
maybeOptions?: MessageBoxOptions
|
||||
): MessageBoxReturnValue {
|
||||
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
|
||||
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
|
||||
export function showMessageBox (windowOrOptions: BaseWindow | MessageBoxOptions, maybeOptions?: MessageBoxOptions): MessageBoxReturnValue {
|
||||
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
|
||||
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
|
||||
return messageBox(false, window, options);
|
||||
}
|
||||
|
||||
export function showMessageBoxSync(window: BaseWindow, options: MessageBoxOptions): MessageBoxReturnValue;
|
||||
export function showMessageBoxSync(options: MessageBoxOptions): MessageBoxReturnValue;
|
||||
export function showMessageBoxSync(
|
||||
windowOrOptions: BaseWindow | MessageBoxOptions,
|
||||
maybeOptions?: MessageBoxOptions
|
||||
): MessageBoxReturnValue {
|
||||
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
|
||||
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
|
||||
export function showMessageBoxSync (windowOrOptions: BaseWindow | MessageBoxOptions, maybeOptions?: MessageBoxOptions): MessageBoxReturnValue {
|
||||
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
|
||||
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
|
||||
return messageBox(true, window, options);
|
||||
}
|
||||
|
||||
export function showErrorBox(...args: any[]) {
|
||||
export function showErrorBox (...args: any[]) {
|
||||
return dialogBinding.showErrorBox(...args);
|
||||
}
|
||||
|
||||
export function showCertificateTrustDialog(
|
||||
windowOrOptions: BaseWindow | CertificateTrustDialogOptions,
|
||||
maybeOptions?: CertificateTrustDialogOptions
|
||||
) {
|
||||
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
|
||||
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
|
||||
export function showCertificateTrustDialog (windowOrOptions: BaseWindow | CertificateTrustDialogOptions, maybeOptions?: CertificateTrustDialogOptions) {
|
||||
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
|
||||
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
|
||||
|
||||
if (options == null || typeof options !== 'object') {
|
||||
throw new TypeError('options must be an object');
|
||||
|
||||
@@ -4,12 +4,8 @@ let _inAppPurchase;
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
const { inAppPurchase } = process._linkedBinding('electron_browser_in_app_purchase');
|
||||
const _purchase = inAppPurchase.purchaseProduct as (
|
||||
productID: string,
|
||||
quantity?: number,
|
||||
username?: string
|
||||
) => Promise<boolean>;
|
||||
inAppPurchase.purchaseProduct = (productID: string, opts?: number | { quantity?: number; username?: string }) => {
|
||||
const _purchase = inAppPurchase.purchaseProduct as (productID: string, quantity?: number, username?: string) => Promise<boolean>;
|
||||
inAppPurchase.purchaseProduct = (productID: string, opts?: number | { quantity?: number, username?: string }) => {
|
||||
const quantity = typeof opts === 'object' ? opts.quantity : opts;
|
||||
const username = typeof opts === 'object' ? opts.username : undefined;
|
||||
return _purchase.apply(inAppPurchase, [productID, quantity, username]);
|
||||
|
||||
@@ -1,66 +1,20 @@
|
||||
import {
|
||||
app,
|
||||
BaseWindow,
|
||||
BrowserWindow,
|
||||
session,
|
||||
webContents,
|
||||
WebContents,
|
||||
MenuItemConstructorOptions
|
||||
} from 'electron/main';
|
||||
import { app, BaseWindow, BrowserWindow, session, webContents, WebContents, MenuItemConstructorOptions } from 'electron/main';
|
||||
|
||||
const isMac = process.platform === 'darwin';
|
||||
const isWindows = process.platform === 'win32';
|
||||
const isLinux = process.platform === 'linux';
|
||||
|
||||
type RoleId =
|
||||
| 'about'
|
||||
| 'close'
|
||||
| 'copy'
|
||||
| 'cut'
|
||||
| 'delete'
|
||||
| 'forcereload'
|
||||
| 'front'
|
||||
| 'help'
|
||||
| 'hide'
|
||||
| 'hideothers'
|
||||
| 'minimize'
|
||||
| 'paste'
|
||||
| 'pasteandmatchstyle'
|
||||
| 'quit'
|
||||
| 'redo'
|
||||
| 'reload'
|
||||
| 'resetzoom'
|
||||
| 'selectall'
|
||||
| 'services'
|
||||
| 'recentdocuments'
|
||||
| 'clearrecentdocuments'
|
||||
| 'showsubstitutions'
|
||||
| 'togglesmartquotes'
|
||||
| 'togglesmartdashes'
|
||||
| 'toggletextreplacement'
|
||||
| 'startspeaking'
|
||||
| 'stopspeaking'
|
||||
| 'toggledevtools'
|
||||
| 'togglefullscreen'
|
||||
| 'undo'
|
||||
| 'unhide'
|
||||
| 'window'
|
||||
| 'zoom'
|
||||
| 'zoomin'
|
||||
| 'zoomout'
|
||||
| 'togglespellchecker'
|
||||
| 'appmenu'
|
||||
| 'filemenu'
|
||||
| 'editmenu'
|
||||
| 'viewmenu'
|
||||
| 'windowmenu'
|
||||
| 'sharemenu';
|
||||
type RoleId = 'about' | 'close' | 'copy' | 'cut' | 'delete' | 'forcereload' | 'front' | 'help' | 'hide' | 'hideothers' | 'minimize' |
|
||||
'paste' | 'pasteandmatchstyle' | 'quit' | 'redo' | 'reload' | 'resetzoom' | 'selectall' | 'services' | 'recentdocuments' | 'clearrecentdocuments' |
|
||||
'showsubstitutions' | 'togglesmartquotes' | 'togglesmartdashes' | 'toggletextreplacement' | 'startspeaking' | 'stopspeaking' |
|
||||
'toggledevtools' | 'togglefullscreen' | 'undo' | 'unhide' | 'window' | 'zoom' | 'zoomin' | 'zoomout' | 'togglespellchecker' |
|
||||
'appmenu' | 'filemenu' | 'editmenu' | 'viewmenu' | 'windowmenu' | 'sharemenu'
|
||||
interface Role {
|
||||
label: string;
|
||||
accelerator?: string;
|
||||
checked?: boolean;
|
||||
windowMethod?: (window: BaseWindow) => void;
|
||||
webContentsMethod?: (webContents: WebContents) => void;
|
||||
windowMethod?: ((window: BaseWindow) => void);
|
||||
webContentsMethod?: ((webContents: WebContents) => void);
|
||||
appMethod?: () => void;
|
||||
registerAccelerator?: boolean;
|
||||
nonNativeMacOSRole?: boolean;
|
||||
@@ -69,7 +23,7 @@ interface Role {
|
||||
|
||||
export const roleList: Record<RoleId, Role> = {
|
||||
about: {
|
||||
get label() {
|
||||
get label () {
|
||||
return isLinux ? 'About' : `About ${app.name}`;
|
||||
},
|
||||
...((isWindows || isLinux) && { appMethod: () => app.showAboutPanel() })
|
||||
@@ -77,23 +31,23 @@ export const roleList: Record<RoleId, Role> = {
|
||||
close: {
|
||||
label: isMac ? 'Close Window' : 'Close',
|
||||
accelerator: 'CommandOrControl+W',
|
||||
windowMethod: (w) => w.close()
|
||||
windowMethod: w => w.close()
|
||||
},
|
||||
copy: {
|
||||
label: 'Copy',
|
||||
accelerator: 'CommandOrControl+C',
|
||||
webContentsMethod: (wc) => wc.copy(),
|
||||
webContentsMethod: wc => wc.copy(),
|
||||
registerAccelerator: false
|
||||
},
|
||||
cut: {
|
||||
label: 'Cut',
|
||||
accelerator: 'CommandOrControl+X',
|
||||
webContentsMethod: (wc) => wc.cut(),
|
||||
webContentsMethod: wc => wc.cut(),
|
||||
registerAccelerator: false
|
||||
},
|
||||
delete: {
|
||||
label: 'Delete',
|
||||
webContentsMethod: (wc) => wc.delete()
|
||||
webContentsMethod: wc => wc.delete()
|
||||
},
|
||||
forcereload: {
|
||||
label: 'Force Reload',
|
||||
@@ -112,7 +66,7 @@ export const roleList: Record<RoleId, Role> = {
|
||||
label: 'Help'
|
||||
},
|
||||
hide: {
|
||||
get label() {
|
||||
get label () {
|
||||
return `Hide ${app.name}`;
|
||||
},
|
||||
accelerator: 'Command+H'
|
||||
@@ -124,31 +78,28 @@ export const roleList: Record<RoleId, Role> = {
|
||||
minimize: {
|
||||
label: 'Minimize',
|
||||
accelerator: 'CommandOrControl+M',
|
||||
windowMethod: (w) => {
|
||||
windowMethod: w => {
|
||||
if (w.minimizable) w.minimize();
|
||||
}
|
||||
},
|
||||
paste: {
|
||||
label: 'Paste',
|
||||
accelerator: 'CommandOrControl+V',
|
||||
webContentsMethod: (wc) => wc.paste(),
|
||||
webContentsMethod: wc => wc.paste(),
|
||||
registerAccelerator: false
|
||||
},
|
||||
pasteandmatchstyle: {
|
||||
label: 'Paste and Match Style',
|
||||
accelerator: isMac ? 'Cmd+Option+Shift+V' : 'Shift+CommandOrControl+V',
|
||||
webContentsMethod: (wc) => wc.pasteAndMatchStyle(),
|
||||
webContentsMethod: wc => wc.pasteAndMatchStyle(),
|
||||
registerAccelerator: false
|
||||
},
|
||||
quit: {
|
||||
get label() {
|
||||
get label () {
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
return `Quit ${app.name}`;
|
||||
case 'win32':
|
||||
return 'Exit';
|
||||
default:
|
||||
return 'Quit';
|
||||
case 'darwin': return `Quit ${app.name}`;
|
||||
case 'win32': return 'Exit';
|
||||
default: return 'Quit';
|
||||
}
|
||||
},
|
||||
accelerator: isWindows ? undefined : 'CommandOrControl+Q',
|
||||
@@ -157,7 +108,7 @@ export const roleList: Record<RoleId, Role> = {
|
||||
redo: {
|
||||
label: 'Redo',
|
||||
accelerator: isWindows ? 'Control+Y' : 'Shift+CommandOrControl+Z',
|
||||
webContentsMethod: (wc) => wc.redo()
|
||||
webContentsMethod: wc => wc.redo()
|
||||
},
|
||||
reload: {
|
||||
label: 'Reload',
|
||||
@@ -180,7 +131,7 @@ export const roleList: Record<RoleId, Role> = {
|
||||
selectall: {
|
||||
label: 'Select All',
|
||||
accelerator: 'CommandOrControl+A',
|
||||
webContentsMethod: (wc) => wc.selectAll()
|
||||
webContentsMethod: wc => wc.selectAll()
|
||||
},
|
||||
services: {
|
||||
label: 'Services'
|
||||
@@ -213,7 +164,7 @@ export const roleList: Record<RoleId, Role> = {
|
||||
label: 'Toggle Developer Tools',
|
||||
accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||
nonNativeMacOSRole: true,
|
||||
webContentsMethod: (wc) => {
|
||||
webContentsMethod: wc => {
|
||||
const bw = wc.getOwnerBrowserWindow();
|
||||
if (bw) bw.webContents.toggleDevTools();
|
||||
}
|
||||
@@ -228,7 +179,7 @@ export const roleList: Record<RoleId, Role> = {
|
||||
undo: {
|
||||
label: 'Undo',
|
||||
accelerator: 'CommandOrControl+Z',
|
||||
webContentsMethod: (wc) => wc.undo()
|
||||
webContentsMethod: wc => wc.undo()
|
||||
},
|
||||
unhide: {
|
||||
label: 'Show All'
|
||||
@@ -257,7 +208,7 @@ export const roleList: Record<RoleId, Role> = {
|
||||
},
|
||||
togglespellchecker: {
|
||||
label: 'Check Spelling While Typing',
|
||||
get checked() {
|
||||
get checked () {
|
||||
const wc = webContents.getFocusedWebContents();
|
||||
const ses = wc ? wc.session : session.defaultSession;
|
||||
return ses.spellCheckerEnabled;
|
||||
@@ -270,7 +221,7 @@ export const roleList: Record<RoleId, Role> = {
|
||||
},
|
||||
// App submenu should be used for Mac only
|
||||
appmenu: {
|
||||
get label() {
|
||||
get label () {
|
||||
return app.name;
|
||||
},
|
||||
submenu: [
|
||||
@@ -288,7 +239,9 @@ export const roleList: Record<RoleId, Role> = {
|
||||
// File submenu
|
||||
filemenu: {
|
||||
label: 'File',
|
||||
submenu: [isMac ? { role: 'close' } : { role: 'quit' }]
|
||||
submenu: [
|
||||
isMac ? { role: 'close' } : { role: 'quit' }
|
||||
]
|
||||
},
|
||||
// Edit submenu
|
||||
editmenu: {
|
||||
@@ -301,27 +254,34 @@ export const roleList: Record<RoleId, Role> = {
|
||||
{ role: 'copy' },
|
||||
{ role: 'paste' },
|
||||
...(isMac
|
||||
? ([
|
||||
{ role: 'pasteAndMatchStyle' },
|
||||
{ role: 'delete' },
|
||||
{ role: 'selectAll' },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Substitutions',
|
||||
submenu: [
|
||||
{ role: 'showSubstitutions' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'toggleSmartQuotes' },
|
||||
{ role: 'toggleSmartDashes' },
|
||||
{ role: 'toggleTextReplacement' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Speech',
|
||||
submenu: [{ role: 'startSpeaking' }, { role: 'stopSpeaking' }]
|
||||
}
|
||||
] as MenuItemConstructorOptions[])
|
||||
: ([{ role: 'delete' }, { type: 'separator' }, { role: 'selectAll' }] as MenuItemConstructorOptions[]))
|
||||
? [
|
||||
{ role: 'pasteAndMatchStyle' },
|
||||
{ role: 'delete' },
|
||||
{ role: 'selectAll' },
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Substitutions',
|
||||
submenu: [
|
||||
{ role: 'showSubstitutions' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'toggleSmartQuotes' },
|
||||
{ role: 'toggleSmartDashes' },
|
||||
{ role: 'toggleTextReplacement' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Speech',
|
||||
submenu: [
|
||||
{ role: 'startSpeaking' },
|
||||
{ role: 'stopSpeaking' }
|
||||
]
|
||||
}
|
||||
] as MenuItemConstructorOptions[]
|
||||
: [
|
||||
{ role: 'delete' },
|
||||
{ type: 'separator' },
|
||||
{ role: 'selectAll' }
|
||||
] as MenuItemConstructorOptions[])
|
||||
]
|
||||
},
|
||||
// View submenu
|
||||
@@ -346,8 +306,13 @@ export const roleList: Record<RoleId, Role> = {
|
||||
{ role: 'minimize' },
|
||||
{ role: 'zoom' },
|
||||
...(isMac
|
||||
? ([{ type: 'separator' }, { role: 'front' }] as MenuItemConstructorOptions[])
|
||||
: ([{ role: 'close' }] as MenuItemConstructorOptions[]))
|
||||
? [
|
||||
{ type: 'separator' },
|
||||
{ role: 'front' }
|
||||
] as MenuItemConstructorOptions[]
|
||||
: [
|
||||
{ role: 'close' }
|
||||
] as MenuItemConstructorOptions[])
|
||||
]
|
||||
},
|
||||
// Share submenu
|
||||
@@ -369,34 +334,34 @@ const canExecuteRole = (role: keyof typeof roleList) => {
|
||||
return roleList[role].nonNativeMacOSRole;
|
||||
};
|
||||
|
||||
export function getDefaultType(role: RoleId) {
|
||||
export function getDefaultType (role: RoleId) {
|
||||
if (shouldOverrideCheckStatus(role)) return 'checkbox';
|
||||
return 'normal';
|
||||
}
|
||||
|
||||
export function getDefaultLabel(role: RoleId) {
|
||||
export function getDefaultLabel (role: RoleId) {
|
||||
return hasRole(role) ? roleList[role].label : '';
|
||||
}
|
||||
|
||||
export function getCheckStatus(role: RoleId) {
|
||||
export function getCheckStatus (role: RoleId) {
|
||||
if (hasRole(role)) return roleList[role].checked;
|
||||
}
|
||||
|
||||
export function shouldOverrideCheckStatus(role: RoleId) {
|
||||
export function shouldOverrideCheckStatus (role: RoleId) {
|
||||
return hasRole(role) && Object.hasOwn(roleList[role], 'checked');
|
||||
}
|
||||
|
||||
export function getDefaultAccelerator(role: RoleId) {
|
||||
export function getDefaultAccelerator (role: RoleId) {
|
||||
if (hasRole(role)) return roleList[role].accelerator;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function shouldRegisterAccelerator(role: RoleId) {
|
||||
export function shouldRegisterAccelerator (role: RoleId) {
|
||||
const hasRoleRegister = hasRole(role) && roleList[role].registerAccelerator !== undefined;
|
||||
return hasRoleRegister ? roleList[role].registerAccelerator : true;
|
||||
}
|
||||
|
||||
export function getDefaultSubmenu(role: RoleId) {
|
||||
export function getDefaultSubmenu (role: RoleId) {
|
||||
if (!hasRole(role)) return;
|
||||
|
||||
let { submenu } = roleList[role];
|
||||
@@ -409,7 +374,7 @@ export function getDefaultSubmenu(role: RoleId) {
|
||||
return submenu;
|
||||
}
|
||||
|
||||
export function execute(role: RoleId, focusedWindow: BaseWindow, focusedWebContents: WebContents) {
|
||||
export function execute (role: RoleId, focusedWindow: BaseWindow, focusedWebContents: WebContents) {
|
||||
if (!canExecuteRole(role)) return false;
|
||||
|
||||
const { appMethod, webContentsMethod, windowMethod } = roleList[role];
|
||||
|
||||
@@ -56,7 +56,8 @@ const MenuItem = function (this: any, options: any) {
|
||||
const click = options.click;
|
||||
this.click = (event: KeyboardEvent, focusedWindow: BaseWindow, focusedWebContents: WebContents) => {
|
||||
// Manually flip the checked flags when clicked.
|
||||
if (!roles.shouldOverrideCheckStatus(this.role) && (this.type === 'checkbox' || this.type === 'radio')) {
|
||||
if (!roles.shouldOverrideCheckStatus(this.role) &&
|
||||
(this.type === 'checkbox' || this.type === 'radio')) {
|
||||
this.checked = !this.checked;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
function splitArray<T>(arr: T[], predicate: (x: T) => boolean) {
|
||||
const result = arr.reduce(
|
||||
(multi, item) => {
|
||||
const current = multi[multi.length - 1];
|
||||
if (predicate(item)) {
|
||||
if (current.length > 0) multi.push([]);
|
||||
} else {
|
||||
current.push(item);
|
||||
}
|
||||
return multi;
|
||||
},
|
||||
[[]] as T[][]
|
||||
);
|
||||
function splitArray<T> (arr: T[], predicate: (x: T) => boolean) {
|
||||
const result = arr.reduce((multi, item) => {
|
||||
const current = multi[multi.length - 1];
|
||||
if (predicate(item)) {
|
||||
if (current.length > 0) multi.push([]);
|
||||
} else {
|
||||
current.push(item);
|
||||
}
|
||||
return multi;
|
||||
}, [[]] as T[][]);
|
||||
|
||||
if (result[result.length - 1].length === 0) {
|
||||
return result.slice(0, result.length - 1);
|
||||
@@ -18,7 +15,7 @@ function splitArray<T>(arr: T[], predicate: (x: T) => boolean) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function joinArrays(arrays: any[][], joinIDs: any[]) {
|
||||
function joinArrays (arrays: any[][], joinIDs: any[]) {
|
||||
return arrays.reduce((joined, arr, i) => {
|
||||
if (i > 0 && arr.length) {
|
||||
if (joinIDs.length > 0) {
|
||||
@@ -32,23 +29,26 @@ function joinArrays(arrays: any[][], joinIDs: any[]) {
|
||||
}, []);
|
||||
}
|
||||
|
||||
function pushOntoMultiMap<K, V>(map: Map<K, V[]>, key: K, value: V) {
|
||||
function pushOntoMultiMap<K, V> (map: Map<K, V[]>, key: K, value: V) {
|
||||
if (!map.has(key)) {
|
||||
map.set(key, []);
|
||||
}
|
||||
map.get(key)!.push(value);
|
||||
}
|
||||
|
||||
function indexOfGroupContainingID<T>(groups: { id?: T }[][], id: T, ignoreGroup: { id?: T }[]) {
|
||||
function indexOfGroupContainingID<T> (groups: {id?: T}[][], id: T, ignoreGroup: {id?: T}[]) {
|
||||
return groups.findIndex(
|
||||
(candidateGroup) =>
|
||||
candidateGroup !== ignoreGroup && candidateGroup.some((candidateItem) => candidateItem.id === id)
|
||||
candidateGroup =>
|
||||
candidateGroup !== ignoreGroup &&
|
||||
candidateGroup.some(
|
||||
candidateItem => candidateItem.id === id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Sort nodes topologically using a depth-first approach. Encountered cycles
|
||||
// are broken.
|
||||
function sortTopologically<T>(originalOrder: T[], edgesById: Map<T, T[]>) {
|
||||
function sortTopologically<T> (originalOrder: T[], edgesById: Map<T, T[]>) {
|
||||
const sorted = [] as T[];
|
||||
const marked = new Set<T>();
|
||||
|
||||
@@ -71,7 +71,7 @@ function sortTopologically<T>(originalOrder: T[], edgesById: Map<T, T[]>) {
|
||||
return sorted;
|
||||
}
|
||||
|
||||
function attemptToMergeAGroup<T>(groups: { before?: T[]; after?: T[]; id?: T }[][]) {
|
||||
function attemptToMergeAGroup<T> (groups: {before?: T[], after?: T[], id?: T}[][]) {
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
const group = groups[i];
|
||||
for (const item of group) {
|
||||
@@ -90,7 +90,7 @@ function attemptToMergeAGroup<T>(groups: { before?: T[]; after?: T[]; id?: T }[]
|
||||
return false;
|
||||
}
|
||||
|
||||
function mergeGroups<T>(groups: { before?: T[]; after?: T[]; id?: T }[][]) {
|
||||
function mergeGroups<T> (groups: {before?: T[], after?: T[], id?: T}[][]) {
|
||||
let merged = true;
|
||||
while (merged) {
|
||||
merged = attemptToMergeAGroup(groups);
|
||||
@@ -98,7 +98,7 @@ function mergeGroups<T>(groups: { before?: T[]; after?: T[]; id?: T }[][]) {
|
||||
return groups;
|
||||
}
|
||||
|
||||
function sortItemsInGroup<T>(group: { before?: T[]; after?: T[]; id?: T }[]) {
|
||||
function sortItemsInGroup<T> (group: {before?: T[], after?: T[], id?: T}[]) {
|
||||
const originalOrder = group.map((node, i) => i);
|
||||
const edges = new Map();
|
||||
const idToIndex = new Map(group.map((item, i) => [item.id, i]));
|
||||
@@ -123,14 +123,10 @@ function sortItemsInGroup<T>(group: { before?: T[]; after?: T[]; id?: T }[]) {
|
||||
}
|
||||
|
||||
const sortedNodes = sortTopologically(originalOrder, edges);
|
||||
return sortedNodes.map((i) => group[i]);
|
||||
return sortedNodes.map(i => group[i]);
|
||||
}
|
||||
|
||||
function findEdgesInGroup<T>(
|
||||
groups: { beforeGroupContaining?: T[]; afterGroupContaining?: T[]; id?: T }[][],
|
||||
i: number,
|
||||
edges: Map<any, any>
|
||||
) {
|
||||
function findEdgesInGroup<T> (groups: {beforeGroupContaining?: T[], afterGroupContaining?: T[], id?: T}[][], i: number, edges: Map<any, any>) {
|
||||
const group = groups[i];
|
||||
for (const item of group) {
|
||||
if (item.beforeGroupContaining) {
|
||||
@@ -154,7 +150,7 @@ function findEdgesInGroup<T>(
|
||||
}
|
||||
}
|
||||
|
||||
function sortGroups<T>(groups: { id?: T }[][]) {
|
||||
function sortGroups<T> (groups: {id?: T}[][]) {
|
||||
const originalOrder = groups.map((item, i) => i);
|
||||
const edges = new Map();
|
||||
|
||||
@@ -163,15 +159,13 @@ function sortGroups<T>(groups: { id?: T }[][]) {
|
||||
}
|
||||
|
||||
const sortedGroupIndexes = sortTopologically(originalOrder, edges);
|
||||
return sortedGroupIndexes.map((i) => groups[i]);
|
||||
return sortedGroupIndexes.map(i => groups[i]);
|
||||
}
|
||||
|
||||
export function sortMenuItems(menuItems: (Electron.MenuItemConstructorOptions | Electron.MenuItem)[]) {
|
||||
export function sortMenuItems (menuItems: (Electron.MenuItemConstructorOptions | Electron.MenuItem)[]) {
|
||||
const isSeparator = (i: Electron.MenuItemConstructorOptions | Electron.MenuItem) => {
|
||||
const opts = i as Electron.MenuItemConstructorOptions;
|
||||
return (
|
||||
i.type === 'separator' && !opts.before && !opts.after && !opts.beforeGroupContaining && !opts.afterGroupContaining
|
||||
);
|
||||
return i.type === 'separator' && !opts.before && !opts.after && !opts.beforeGroupContaining && !opts.afterGroupContaining;
|
||||
};
|
||||
const separators = menuItems.filter(isSeparator);
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ Menu.prototype._executeCommand = function (event, id) {
|
||||
Menu.prototype._menuWillShow = function () {
|
||||
// Ensure radio groups have at least one menu item selected
|
||||
for (const id of Object.keys(this.groupsMap)) {
|
||||
const found = this.groupsMap[id].find((item) => item.checked) || null;
|
||||
const found = this.groupsMap[id].find(item => item.checked) || null;
|
||||
if (!found) checked.set(this.groupsMap[id][0], true);
|
||||
}
|
||||
};
|
||||
@@ -141,7 +141,7 @@ Menu.prototype.closePopup = function (window) {
|
||||
Menu.prototype.getMenuItemById = function (id) {
|
||||
const items = this.items;
|
||||
|
||||
let found = items.find((item) => item.id === id) || null;
|
||||
let found = items.find(item => item.id === id) || null;
|
||||
for (let i = 0; !found && i < items.length; i++) {
|
||||
const { submenu } = items[i];
|
||||
if (submenu) {
|
||||
@@ -213,7 +213,7 @@ Menu.setApplicationMenu = function (menu: MenuType) {
|
||||
bindings.setApplicationMenu(menu);
|
||||
} else {
|
||||
const windows = BaseWindow.getAllWindows();
|
||||
windows.map((w) => w.setMenu(menu));
|
||||
windows.map(w => w.setMenu(menu));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -244,16 +244,14 @@ Menu.buildFromTemplate = function (template) {
|
||||
/* Helper Functions */
|
||||
|
||||
// validate the template against having the wrong attribute
|
||||
function areValidTemplateItems(template: (MenuItemConstructorOptions | MenuItem)[]) {
|
||||
return template.every(
|
||||
(item) =>
|
||||
item != null &&
|
||||
typeof item === 'object' &&
|
||||
(Object.hasOwn(item, 'label') || Object.hasOwn(item, 'role') || item.type === 'separator')
|
||||
);
|
||||
function areValidTemplateItems (template: (MenuItemConstructorOptions | MenuItem)[]) {
|
||||
return template.every(item =>
|
||||
item != null &&
|
||||
typeof item === 'object' &&
|
||||
(Object.hasOwn(item, 'label') || Object.hasOwn(item, 'role') || item.type === 'separator'));
|
||||
}
|
||||
|
||||
function sortTemplate(template: (MenuItemConstructorOptions | MenuItem)[]) {
|
||||
function sortTemplate (template: (MenuItemConstructorOptions | MenuItem)[]) {
|
||||
const sorted = sortMenuItems(template);
|
||||
for (const item of sorted) {
|
||||
if (Array.isArray(item.submenu)) {
|
||||
@@ -264,7 +262,7 @@ function sortTemplate(template: (MenuItemConstructorOptions | MenuItem)[]) {
|
||||
}
|
||||
|
||||
// Search between separators to find a radio menu item and return its group id
|
||||
function generateGroupId(items: (MenuItemConstructorOptions | MenuItem)[], pos: number) {
|
||||
function generateGroupId (items: (MenuItemConstructorOptions | MenuItem)[], pos: number) {
|
||||
if (pos > 0) {
|
||||
for (let idx = pos - 1; idx >= 0; idx--) {
|
||||
if (items[idx].type === 'radio') return (items[idx] as MenuItem).groupId;
|
||||
@@ -280,7 +278,7 @@ function generateGroupId(items: (MenuItemConstructorOptions | MenuItem)[], pos:
|
||||
return groupIdIndex;
|
||||
}
|
||||
|
||||
function removeExtraSeparators(items: (MenuItemConstructorOptions | MenuItem)[]) {
|
||||
function removeExtraSeparators (items: (MenuItemConstructorOptions | MenuItem)[]) {
|
||||
// fold adjacent separators together
|
||||
let ret = items.filter((e, idx, arr) => {
|
||||
if (e.visible === false) return true;
|
||||
@@ -296,7 +294,7 @@ function removeExtraSeparators(items: (MenuItemConstructorOptions | MenuItem)[])
|
||||
return ret;
|
||||
}
|
||||
|
||||
function insertItemByType(this: MenuType, item: MenuItem, pos: number) {
|
||||
function insertItemByType (this: MenuType, item: MenuItem, pos: number) {
|
||||
const types = {
|
||||
normal: () => this.insertItem(pos, item.commandId, item.label),
|
||||
header: () => this.insertItem(pos, item.commandId, item.label),
|
||||
|
||||
@@ -7,7 +7,7 @@ const { createPair } = process._linkedBinding('electron_browser_message_port');
|
||||
export default class MessageChannelMain extends EventEmitter implements Electron.MessageChannelMain {
|
||||
port1: MessagePortMain;
|
||||
port2: MessagePortMain;
|
||||
constructor() {
|
||||
constructor () {
|
||||
super();
|
||||
const { port1, port2 } = createPair();
|
||||
this.port1 = new MessagePortMain(port1);
|
||||
|
||||
@@ -4,11 +4,7 @@ import { ClientRequestConstructorOptions, ClientRequest, IncomingMessage, Sessio
|
||||
|
||||
import { Readable, Writable, isReadable } from 'stream';
|
||||
|
||||
function createDeferredPromise<T, E extends Error = Error>(): {
|
||||
promise: Promise<T>;
|
||||
resolve: (x: T) => void;
|
||||
reject: (e: E) => void;
|
||||
} {
|
||||
function createDeferredPromise<T, E extends Error = Error> (): { promise: Promise<T>; resolve: (x: T) => void; reject: (e: E) => void; } {
|
||||
let res: (x: T) => void;
|
||||
let rej: (e: E) => void;
|
||||
const promise = new Promise<T>((resolve, reject) => {
|
||||
@@ -19,12 +15,8 @@ function createDeferredPromise<T, E extends Error = Error>(): {
|
||||
return { promise, resolve: res!, reject: rej! };
|
||||
}
|
||||
|
||||
export function fetchWithSession(
|
||||
input: RequestInfo,
|
||||
init: (RequestInit & { bypassCustomProtocolHandlers?: boolean }) | undefined,
|
||||
session: SessionT | undefined,
|
||||
request: (options: ClientRequestConstructorOptions | string) => ClientRequest
|
||||
) {
|
||||
export function fetchWithSession (input: RequestInfo, init: (RequestInit & {bypassCustomProtocolHandlers?: boolean}) | undefined, session: SessionT | undefined,
|
||||
request: (options: ClientRequestConstructorOptions | string) => ClientRequest) {
|
||||
const p = createDeferredPromise<Response>();
|
||||
let req: Request;
|
||||
try {
|
||||
@@ -84,18 +76,16 @@ export function fetchWithSession(
|
||||
// We can't set credentials to same-origin unless there's an origin set.
|
||||
const credentials = req.credentials === 'same-origin' && !origin ? 'include' : req.credentials;
|
||||
|
||||
const r = request(
|
||||
allowAnyProtocol({
|
||||
session,
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
origin,
|
||||
credentials,
|
||||
cache: req.cache,
|
||||
referrerPolicy: req.referrerPolicy,
|
||||
redirect: req.redirect
|
||||
})
|
||||
);
|
||||
const r = request(allowAnyProtocol({
|
||||
session,
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
origin,
|
||||
credentials,
|
||||
cache: req.cache,
|
||||
referrerPolicy: req.referrerPolicy,
|
||||
redirect: req.redirect
|
||||
}));
|
||||
|
||||
(r as any)._urlLoaderOptions.bypassCustomProtocolHandlers = !!init?.bypassCustomProtocolHandlers;
|
||||
|
||||
@@ -115,10 +105,7 @@ export function fetchWithSession(
|
||||
headers.set(k, Array.isArray(v) ? v.join(', ') : v);
|
||||
}
|
||||
const nullBodyStatus = [101, 204, 205, 304];
|
||||
const body =
|
||||
nullBodyStatus.includes(resp.statusCode) || req.method === 'HEAD'
|
||||
? null
|
||||
: (Readable.toWeb(resp as unknown as Readable) as ReadableStream);
|
||||
const body = nullBodyStatus.includes(resp.statusCode) || req.method === 'HEAD' ? null : Readable.toWeb(resp as unknown as Readable) as ReadableStream;
|
||||
const rResp = new Response(body, {
|
||||
headers,
|
||||
status: resp.statusCode,
|
||||
@@ -135,9 +122,7 @@ export function fetchWithSession(
|
||||
// pipeTo expects a WritableStream<Uint8Array>. Node.js' Writable.toWeb returns WritableStream<any>,
|
||||
// which causes a TS structural mismatch.
|
||||
const writable = Writable.toWeb(r as unknown as Writable) as unknown as WritableStream<Uint8Array>;
|
||||
if (!req.body?.pipeTo(writable).then(() => r.end())) {
|
||||
r.end();
|
||||
}
|
||||
if (!req.body?.pipeTo(writable).then(() => r.end())) { r.end(); }
|
||||
|
||||
return p.promise;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ const stopLogging: typeof session.defaultSession.netLog.stopLogging = async () =
|
||||
export default {
|
||||
startLogging,
|
||||
stopLogging,
|
||||
get currentlyLogging(): boolean {
|
||||
get currentlyLogging (): boolean {
|
||||
if (!app.isReady()) return false;
|
||||
return session.defaultSession.netLog.currentlyLogging;
|
||||
}
|
||||
|
||||
@@ -5,21 +5,18 @@ import type { ClientRequestConstructorOptions } from 'electron/main';
|
||||
|
||||
const { isOnline } = process._linkedBinding('electron_common_net');
|
||||
|
||||
export function request(
|
||||
options: ClientRequestConstructorOptions | string,
|
||||
callback?: (message: IncomingMessage) => void
|
||||
) {
|
||||
export function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
|
||||
if (!app.isReady()) {
|
||||
throw new Error('net module can only be used after app is ready');
|
||||
}
|
||||
return new ClientRequest(options, callback);
|
||||
}
|
||||
|
||||
export function fetch(input: RequestInfo, init?: RequestInit): Promise<Response> {
|
||||
export function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
|
||||
return session.defaultSession.fetch(input, init);
|
||||
}
|
||||
|
||||
export function resolveHost(host: string, options?: Electron.ResolveHostOptions): Promise<Electron.ResolvedHost> {
|
||||
export function resolveHost (host: string, options?: Electron.ResolveHostOptions): Promise<Electron.ResolvedHost> {
|
||||
return session.defaultSession.resolveHost(host, options);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
const { createPowerMonitor, getSystemIdleState, getSystemIdleTime, getCurrentThermalState, isOnBatteryPower } =
|
||||
process._linkedBinding('electron_browser_power_monitor');
|
||||
const {
|
||||
createPowerMonitor,
|
||||
getSystemIdleState,
|
||||
getSystemIdleTime,
|
||||
getCurrentThermalState,
|
||||
isOnBatteryPower
|
||||
} = process._linkedBinding('electron_browser_power_monitor');
|
||||
|
||||
// Hold the native PowerMonitor at module level so it is never garbage-collected
|
||||
// while this module is alive. The C++ side registers OS-level callbacks (HWND
|
||||
@@ -10,7 +15,7 @@ const { createPowerMonitor, getSystemIdleState, getSystemIdleTime, getCurrentThe
|
||||
let pm: any;
|
||||
|
||||
class PowerMonitor extends EventEmitter implements Electron.PowerMonitor {
|
||||
constructor() {
|
||||
constructor () {
|
||||
super();
|
||||
// Don't start the event source until both a) the app is ready and b)
|
||||
// there's a listener registered for a powerMonitor event.
|
||||
@@ -38,23 +43,23 @@ class PowerMonitor extends EventEmitter implements Electron.PowerMonitor {
|
||||
});
|
||||
}
|
||||
|
||||
getSystemIdleState(idleThreshold: number) {
|
||||
getSystemIdleState (idleThreshold: number) {
|
||||
return getSystemIdleState(idleThreshold);
|
||||
}
|
||||
|
||||
getCurrentThermalState() {
|
||||
getCurrentThermalState () {
|
||||
return getCurrentThermalState();
|
||||
}
|
||||
|
||||
getSystemIdleTime() {
|
||||
getSystemIdleTime () {
|
||||
return getSystemIdleTime();
|
||||
}
|
||||
|
||||
isOnBatteryPower() {
|
||||
isOnBatteryPower () {
|
||||
return isOnBatteryPower();
|
||||
}
|
||||
|
||||
get onBatteryPower() {
|
||||
get onBatteryPower () {
|
||||
return this.isOnBatteryPower();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,18 +7,17 @@ import { ReadableStream } from 'stream/web';
|
||||
import type { ReadableStreamDefaultReader } from 'stream/web';
|
||||
|
||||
// Global protocol APIs.
|
||||
const { registerSchemesAsPrivileged, getStandardSchemes, Protocol } =
|
||||
process._linkedBinding('electron_browser_protocol');
|
||||
const { registerSchemesAsPrivileged, getStandardSchemes, Protocol } = process._linkedBinding('electron_browser_protocol');
|
||||
|
||||
const ERR_FAILED = -2;
|
||||
const ERR_UNEXPECTED = -9;
|
||||
|
||||
const isBuiltInScheme = (scheme: string) => ['http', 'https', 'file'].includes(scheme);
|
||||
|
||||
function makeStreamFromPipe(pipe: any): ReadableStream<Uint8Array> {
|
||||
function makeStreamFromPipe (pipe: any): ReadableStream<Uint8Array> {
|
||||
const buf = new Uint8Array(1024 * 1024 /* 1 MB */);
|
||||
return new ReadableStream({
|
||||
async pull(controller) {
|
||||
async pull (controller) {
|
||||
try {
|
||||
const rv = await pipe.read(buf);
|
||||
if (rv > 0) {
|
||||
@@ -33,7 +32,7 @@ function makeStreamFromPipe(pipe: any): ReadableStream<Uint8Array> {
|
||||
});
|
||||
}
|
||||
|
||||
function makeStreamFromFileInfo({
|
||||
function makeStreamFromFileInfo ({
|
||||
filePath,
|
||||
offset = 0,
|
||||
length = -1
|
||||
@@ -43,15 +42,13 @@ function makeStreamFromFileInfo({
|
||||
length?: number;
|
||||
}): ReadableStream<Uint8Array> {
|
||||
// Node's Readable.toWeb produces a WHATWG ReadableStream whose chunks are Uint8Array.
|
||||
return Readable.toWeb(
|
||||
createReadStream(filePath, {
|
||||
start: offset,
|
||||
end: length >= 0 ? offset + length : undefined
|
||||
})
|
||||
) as ReadableStream<Uint8Array>;
|
||||
return Readable.toWeb(createReadStream(filePath, {
|
||||
start: offset,
|
||||
end: length >= 0 ? offset + length : undefined
|
||||
})) as ReadableStream<Uint8Array>;
|
||||
}
|
||||
|
||||
function convertToRequestBody(uploadData: ProtocolRequest['uploadData']): RequestInit['body'] {
|
||||
function convertToRequestBody (uploadData: ProtocolRequest['uploadData']): RequestInit['body'] {
|
||||
if (!uploadData) return null;
|
||||
// Optimization: skip creating a stream if the request is just a single buffer.
|
||||
if (uploadData.length === 1 && (uploadData[0] as any).type === 'rawData') {
|
||||
@@ -63,7 +60,7 @@ function convertToRequestBody(uploadData: ProtocolRequest['uploadData']): Reques
|
||||
// Generic <Uint8Array> ensures reader.read() returns value?: Uint8Array consistent with enqueue.
|
||||
let current: ReadableStreamDefaultReader<Uint8Array> | null = null;
|
||||
return new ReadableStream<Uint8Array>({
|
||||
async pull(controller) {
|
||||
async pull (controller) {
|
||||
if (current) {
|
||||
const { done, value } = await current.read();
|
||||
// (done => value === undefined) as per WHATWG spec
|
||||
@@ -74,9 +71,7 @@ function convertToRequestBody(uploadData: ProtocolRequest['uploadData']): Reques
|
||||
controller.enqueue(value);
|
||||
}
|
||||
} else {
|
||||
if (!chunks.length) {
|
||||
return controller.close();
|
||||
}
|
||||
if (!chunks.length) { return controller.close(); }
|
||||
const chunk = chunks.shift()!;
|
||||
if (chunk.type === 'rawData') {
|
||||
controller.enqueue(chunk.bytes as Uint8Array);
|
||||
@@ -101,7 +96,7 @@ function convertToRequestBody(uploadData: ProtocolRequest['uploadData']): Reques
|
||||
}) as RequestInit['body'];
|
||||
}
|
||||
|
||||
function validateResponse(res: Response) {
|
||||
function validateResponse (res: Response) {
|
||||
if (!res || typeof res !== 'object') return false;
|
||||
|
||||
if (res.type === 'error') return true;
|
||||
@@ -120,11 +115,7 @@ function validateResponse(res: Response) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Protocol.prototype.handle = function (
|
||||
this: Electron.Protocol,
|
||||
scheme: string,
|
||||
handler: (req: Request) => Response | Promise<Response>
|
||||
) {
|
||||
Protocol.prototype.handle = function (this: Electron.Protocol, scheme: string, handler: (req: Request) => Response | Promise<Response>) {
|
||||
const register = isBuiltInScheme(scheme) ? this.interceptProtocol : this.registerProtocol;
|
||||
const success = register.call(this, scheme, async (preq: ProtocolRequest, cb: any) => {
|
||||
try {
|
||||
@@ -164,9 +155,7 @@ Protocol.prototype.handle = function (
|
||||
|
||||
Protocol.prototype.unhandle = function (this: Electron.Protocol, scheme: string) {
|
||||
const unregister = isBuiltInScheme(scheme) ? this.uninterceptProtocol : this.unregisterProtocol;
|
||||
if (!unregister.call(this, scheme)) {
|
||||
throw new Error(`Failed to unhandle protocol: ${scheme}`);
|
||||
}
|
||||
if (!unregister.call(this, scheme)) { throw new Error(`Failed to unhandle protocol: ${scheme}`); }
|
||||
};
|
||||
|
||||
Protocol.prototype.isProtocolHandled = function (this: Electron.Protocol, scheme: string) {
|
||||
|
||||
@@ -14,38 +14,35 @@ const createScreenIfNeeded = () => {
|
||||
// exposes an instance created by createScreen. In order to avoid
|
||||
// side-effecting and calling createScreen upon import of this module, instead
|
||||
// we export a proxy which lazily calls createScreen on first access.
|
||||
export default new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (target, property: keyof Electron.Screen) => {
|
||||
createScreenIfNeeded();
|
||||
const value = _screen[property];
|
||||
if (typeof value === 'function') {
|
||||
return value.bind(_screen);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
set: (target, property: string, value: unknown) => {
|
||||
createScreenIfNeeded();
|
||||
return Reflect.set(_screen, property, value);
|
||||
},
|
||||
ownKeys: () => {
|
||||
createScreenIfNeeded();
|
||||
return Reflect.ownKeys(_screen);
|
||||
},
|
||||
has: (target, property: string) => {
|
||||
createScreenIfNeeded();
|
||||
return property in _screen;
|
||||
},
|
||||
getOwnPropertyDescriptor: (target, property: string) => {
|
||||
createScreenIfNeeded();
|
||||
return Reflect.getOwnPropertyDescriptor(_screen, property);
|
||||
},
|
||||
getPrototypeOf: () => {
|
||||
// This is necessary as a result of weirdness with EventEmitterMixin
|
||||
// and FunctionTemplate - we need to explicitly ensure it's returned
|
||||
// in the prototype.
|
||||
return EventEmitter.prototype;
|
||||
export default new Proxy({}, {
|
||||
get: (target, property: keyof Electron.Screen) => {
|
||||
createScreenIfNeeded();
|
||||
const value = _screen[property];
|
||||
if (typeof value === 'function') {
|
||||
return value.bind(_screen);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
set: (target, property: string, value: unknown) => {
|
||||
createScreenIfNeeded();
|
||||
return Reflect.set(_screen, property, value);
|
||||
},
|
||||
ownKeys: () => {
|
||||
createScreenIfNeeded();
|
||||
return Reflect.ownKeys(_screen);
|
||||
},
|
||||
has: (target, property: string) => {
|
||||
createScreenIfNeeded();
|
||||
return property in _screen;
|
||||
},
|
||||
getOwnPropertyDescriptor: (target, property: string) => {
|
||||
createScreenIfNeeded();
|
||||
return Reflect.getOwnPropertyDescriptor(_screen, property);
|
||||
},
|
||||
getPrototypeOf: () => {
|
||||
// This is necessary as a result of weirdness with EventEmitterMixin
|
||||
// and FunctionTemplate - we need to explicitly ensure it's returned
|
||||
// in the prototype.
|
||||
return EventEmitter.prototype;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
|
||||
const { ServiceWorkerMain } = process._linkedBinding('electron_browser_service_worker_main');
|
||||
|
||||
Object.defineProperty(ServiceWorkerMain.prototype, 'ipc', {
|
||||
get() {
|
||||
get () {
|
||||
const ipc = new IpcMainImpl();
|
||||
Object.defineProperty(this, 'ipc', { value: ipc });
|
||||
return ipc;
|
||||
|
||||
@@ -15,7 +15,7 @@ let fakeVideoWindowId = -1;
|
||||
const kMacOsNativePickerId = -4;
|
||||
const systemPickerVideoSource = Object.create(null);
|
||||
Object.defineProperty(systemPickerVideoSource, 'id', {
|
||||
get() {
|
||||
get () {
|
||||
return `window:${kMacOsNativePickerId}:${fakeVideoWindowId--}`;
|
||||
}
|
||||
});
|
||||
@@ -73,18 +73,13 @@ Session.prototype.setPreloads = function (preloads) {
|
||||
.forEach((script) => {
|
||||
this.unregisterPreloadScript(script.id);
|
||||
});
|
||||
preloads
|
||||
.map(
|
||||
(filePath) =>
|
||||
({
|
||||
type: 'frame',
|
||||
filePath,
|
||||
_deprecated: true
|
||||
}) as Electron.PreloadScriptRegistration
|
||||
)
|
||||
.forEach((script) => {
|
||||
this.registerPreloadScript(script);
|
||||
});
|
||||
preloads.map(filePath => ({
|
||||
type: 'frame',
|
||||
filePath,
|
||||
_deprecated: true
|
||||
}) as Electron.PreloadScriptRegistration).forEach(script => {
|
||||
this.registerPreloadScript(script);
|
||||
});
|
||||
};
|
||||
|
||||
Session.prototype.getAllExtensions = deprecate.moveAPI(
|
||||
@@ -119,7 +114,7 @@ Session.prototype.removeExtension = deprecate.moveAPI(
|
||||
export default {
|
||||
fromPartition,
|
||||
fromPath,
|
||||
get defaultSession() {
|
||||
get defaultSession () {
|
||||
return fromPartition('');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,16 +5,16 @@ import { EventEmitter } from 'events';
|
||||
class ShareMenu extends EventEmitter implements Electron.ShareMenu {
|
||||
private menu: Menu;
|
||||
|
||||
constructor(sharingItem: SharingItem) {
|
||||
constructor (sharingItem: SharingItem) {
|
||||
super();
|
||||
this.menu = new (Menu as any)({ sharingItem });
|
||||
}
|
||||
|
||||
popup(options?: PopupOptions) {
|
||||
popup (options?: PopupOptions) {
|
||||
this.menu.popup(options);
|
||||
}
|
||||
|
||||
closePopup(browserWindow?: BrowserWindow) {
|
||||
closePopup (browserWindow?: BrowserWindow) {
|
||||
this.menu.closePopup(browserWindow);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,41 +14,36 @@ type SharedTextureImportedWrapper = {
|
||||
texture: Electron.SharedTextureImported;
|
||||
allReferencesReleased: AllReleasedCallback | undefined;
|
||||
mainReference: boolean;
|
||||
rendererFrameReferences: Map<number, { count: number; reference: Electron.WebFrameMain }>;
|
||||
};
|
||||
rendererFrameReferences: Map<number, { count: number, reference: Electron.WebFrameMain }>;
|
||||
}
|
||||
|
||||
ipcMain.handle(
|
||||
IPC_MESSAGES.IMPORT_SHARED_TEXTURE_RELEASE_RENDERER_TO_MAIN,
|
||||
(event: Electron.IpcMainInvokeEvent, textureId: string) => {
|
||||
const frameTreeNodeId = event.frameTreeNodeId ?? event.sender.mainFrame.frameTreeNodeId;
|
||||
wrapperReleaseFromRenderer(textureId, frameTreeNodeId);
|
||||
}
|
||||
);
|
||||
ipcMain.handle(IPC_MESSAGES.IMPORT_SHARED_TEXTURE_RELEASE_RENDERER_TO_MAIN, (event: Electron.IpcMainInvokeEvent, textureId: string) => {
|
||||
const frameTreeNodeId = event.frameTreeNodeId ?? event.sender.mainFrame.frameTreeNodeId;
|
||||
wrapperReleaseFromRenderer(textureId, frameTreeNodeId);
|
||||
});
|
||||
|
||||
let checkManagedSharedTexturesInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
function scheduleCheckManagedSharedTextures() {
|
||||
function scheduleCheckManagedSharedTextures () {
|
||||
if (checkManagedSharedTexturesInterval === null) {
|
||||
checkManagedSharedTexturesInterval = setInterval(checkManagedSharedTextures, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function unscheduleCheckManagedSharedTextures() {
|
||||
function unscheduleCheckManagedSharedTextures () {
|
||||
if (checkManagedSharedTexturesInterval !== null) {
|
||||
clearInterval(checkManagedSharedTexturesInterval);
|
||||
checkManagedSharedTexturesInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
function checkManagedSharedTextures() {
|
||||
function checkManagedSharedTextures () {
|
||||
const texturesToRemoveTracking = new Set<string>();
|
||||
for (const [, wrapper] of managedSharedTextures) {
|
||||
for (const [frameTreeNodeId, entry] of wrapper.rendererFrameReferences) {
|
||||
const frame = entry.reference;
|
||||
if (!frame || frame.isDestroyed()) {
|
||||
console.error(
|
||||
`The imported shared texture ${wrapper.texture.textureId} is referenced by a destroyed webContent/webFrameMain, this means a imported shared texture in renderer process is not released before the process is exited. Releasing that dangling reference now.`
|
||||
);
|
||||
console.error(`The imported shared texture ${wrapper.texture.textureId} is referenced by a destroyed webContent/webFrameMain, this means a imported shared texture in renderer process is not released before the process is exited. Releasing that dangling reference now.`);
|
||||
wrapper.rendererFrameReferences.delete(frameTreeNodeId);
|
||||
}
|
||||
}
|
||||
@@ -70,7 +65,7 @@ function checkManagedSharedTextures() {
|
||||
}
|
||||
}
|
||||
|
||||
function wrapperReleaseFromRenderer(id: string, frameTreeNodeId: number) {
|
||||
function wrapperReleaseFromRenderer (id: string, frameTreeNodeId: number) {
|
||||
const wrapper = managedSharedTextures.get(id);
|
||||
if (!wrapper) {
|
||||
throw new Error(`Shared texture with id ${id} not found`);
|
||||
@@ -97,7 +92,7 @@ function wrapperReleaseFromRenderer(id: string, frameTreeNodeId: number) {
|
||||
}
|
||||
}
|
||||
|
||||
function wrapperReleaseFromMain(id: string) {
|
||||
function wrapperReleaseFromMain (id: string) {
|
||||
const wrapper = managedSharedTextures.get(id);
|
||||
if (!wrapper) {
|
||||
throw new Error(`Shared texture with id ${id} not found`);
|
||||
@@ -113,18 +108,14 @@ function wrapperReleaseFromMain(id: string) {
|
||||
}
|
||||
}
|
||||
|
||||
async function sendSharedTexture(options: Electron.SendSharedTextureOptions, ...args: any[]) {
|
||||
async function sendSharedTexture (options: Electron.SendSharedTextureOptions, ...args: any[]) {
|
||||
const imported = options.importedSharedTexture;
|
||||
const transfer = imported.subtle.startTransferSharedTexture();
|
||||
|
||||
let timeoutHandle: NodeJS.Timeout | null = null;
|
||||
const timeoutPromise = new Promise<never>((resolve, reject) => {
|
||||
timeoutHandle = setTimeout(() => {
|
||||
reject(
|
||||
new Error(
|
||||
`transfer shared texture timed out after ${transferTimeout}ms, ensure you have registered receiver at renderer process.`
|
||||
)
|
||||
);
|
||||
reject(new Error(`transfer shared texture timed out after ${transferTimeout}ms, ensure you have registered receiver at renderer process.`));
|
||||
}, transferTimeout);
|
||||
});
|
||||
|
||||
@@ -133,14 +124,13 @@ async function sendSharedTexture(options: Electron.SendSharedTextureOptions, ...
|
||||
throw new Error('`frame` should be provided');
|
||||
}
|
||||
|
||||
const invokePromise: Promise<Electron.SharedTextureSyncToken> =
|
||||
ipcMainInternalUtils.invokeInWebFrameMain<Electron.SharedTextureSyncToken>(
|
||||
targetFrame,
|
||||
IPC_MESSAGES.IMPORT_SHARED_TEXTURE_TRANSFER_MAIN_TO_RENDERER,
|
||||
transfer,
|
||||
imported.textureId,
|
||||
...args
|
||||
);
|
||||
const invokePromise: Promise<Electron.SharedTextureSyncToken> = ipcMainInternalUtils.invokeInWebFrameMain<Electron.SharedTextureSyncToken>(
|
||||
targetFrame,
|
||||
IPC_MESSAGES.IMPORT_SHARED_TEXTURE_TRANSFER_MAIN_TO_RENDERER,
|
||||
transfer,
|
||||
imported.textureId,
|
||||
...args
|
||||
);
|
||||
|
||||
try {
|
||||
const syncToken = await Promise.race([invokePromise, timeoutPromise]);
|
||||
@@ -169,7 +159,7 @@ async function sendSharedTexture(options: Electron.SendSharedTextureOptions, ...
|
||||
scheduleCheckManagedSharedTextures();
|
||||
}
|
||||
|
||||
function importSharedTexture(options: Electron.ImportSharedTextureOptions) {
|
||||
function importSharedTexture (options: Electron.ImportSharedTextureOptions) {
|
||||
const id = randomUUID();
|
||||
const imported = sharedTextureNative.importSharedTexture(Object.assign(options.textureInfo, { id }));
|
||||
const ret: Electron.SharedTextureImported = {
|
||||
|
||||
@@ -10,10 +10,7 @@ if ('getEffectiveAppearance' in systemPreferences) {
|
||||
}
|
||||
|
||||
if ('accessibilityDisplayShouldReduceTransparency' in systemPreferences) {
|
||||
const reduceTransparencyDeprecated = deprecate.warnOnce(
|
||||
'systemPreferences.accessibilityDisplayShouldReduceTransparency',
|
||||
'nativeTheme.prefersReducedTransparency'
|
||||
);
|
||||
const reduceTransparencyDeprecated = deprecate.warnOnce('systemPreferences.accessibilityDisplayShouldReduceTransparency', 'nativeTheme.prefersReducedTransparency');
|
||||
const nativeReduceTransparency = systemPreferences.accessibilityDisplayShouldReduceTransparency;
|
||||
Object.defineProperty(systemPreferences, 'accessibilityDisplayShouldReduceTransparency', {
|
||||
get: () => {
|
||||
|
||||
@@ -12,53 +12,41 @@ const extendConstructHook = (target: any, hook: Function) => {
|
||||
};
|
||||
};
|
||||
|
||||
const ImmutableProperty =
|
||||
<T extends TouchBarItem<any>>(
|
||||
def: (
|
||||
config: T extends TouchBarItem<infer C> ? C : never,
|
||||
setInternalProp: <K extends keyof T>(k: K, v: T[K]) => void
|
||||
) => any
|
||||
) =>
|
||||
(target: T, propertyKey: keyof T) => {
|
||||
extendConstructHook(target, function (this: T) {
|
||||
(this as any)[hiddenProperties][propertyKey] = def((this as any)._config, (k, v) => {
|
||||
(this as any)[hiddenProperties][k] = v;
|
||||
});
|
||||
const ImmutableProperty = <T extends TouchBarItem<any>>(def: (config: T extends TouchBarItem<infer C> ? C : never, setInternalProp: <K extends keyof T>(k: K, v: T[K]) => void) => any) => (target: T, propertyKey: keyof T) => {
|
||||
extendConstructHook(target, function (this: T) {
|
||||
(this as any)[hiddenProperties][propertyKey] = def((this as any)._config, (k, v) => {
|
||||
(this as any)[hiddenProperties][k] = v;
|
||||
});
|
||||
Object.defineProperty(target, propertyKey, {
|
||||
get: function () {
|
||||
return this[hiddenProperties][propertyKey];
|
||||
},
|
||||
set: function () {
|
||||
throw new Error(`Cannot override property ${name}`);
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: false
|
||||
});
|
||||
};
|
||||
});
|
||||
Object.defineProperty(target, propertyKey, {
|
||||
get: function () {
|
||||
return this[hiddenProperties][propertyKey];
|
||||
},
|
||||
set: function () {
|
||||
throw new Error(`Cannot override property ${name}`);
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: false
|
||||
});
|
||||
};
|
||||
|
||||
const LiveProperty =
|
||||
<T extends TouchBarItem<any>>(
|
||||
def: (config: T extends TouchBarItem<infer C> ? C : never) => any,
|
||||
onMutate?: (self: T, newValue: any) => void
|
||||
) =>
|
||||
(target: T, propertyKey: keyof T) => {
|
||||
extendConstructHook(target, function (this: T) {
|
||||
(this as any)[hiddenProperties][propertyKey] = def((this as any)._config);
|
||||
if (onMutate) onMutate(this as any, (this as any)[hiddenProperties][propertyKey]);
|
||||
});
|
||||
Object.defineProperty(target, propertyKey, {
|
||||
get: function () {
|
||||
return this[hiddenProperties][propertyKey];
|
||||
},
|
||||
set: function (value) {
|
||||
if (onMutate) onMutate(this as any, value);
|
||||
this[hiddenProperties][propertyKey] = value;
|
||||
this.emit('change', this);
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
};
|
||||
const LiveProperty = <T extends TouchBarItem<any>>(def: (config: T extends TouchBarItem<infer C> ? C : never) => any, onMutate?: (self: T, newValue: any) => void) => (target: T, propertyKey: keyof T) => {
|
||||
extendConstructHook(target, function (this: T) {
|
||||
(this as any)[hiddenProperties][propertyKey] = def((this as any)._config);
|
||||
if (onMutate) onMutate((this as any), (this as any)[hiddenProperties][propertyKey]);
|
||||
});
|
||||
Object.defineProperty(target, propertyKey, {
|
||||
get: function () {
|
||||
return this[hiddenProperties][propertyKey];
|
||||
},
|
||||
set: function (value) {
|
||||
if (onMutate) onMutate((this as any), value);
|
||||
this[hiddenProperties][propertyKey] = value;
|
||||
this.emit('change', this);
|
||||
},
|
||||
enumerable: true
|
||||
});
|
||||
};
|
||||
|
||||
abstract class TouchBarItem<ConfigType> extends EventEmitter {
|
||||
@ImmutableProperty(() => `${nextItemID++}`) id!: string;
|
||||
@@ -69,17 +57,17 @@ abstract class TouchBarItem<ConfigType> extends EventEmitter {
|
||||
private _parents: { id: string; type: string }[] = [];
|
||||
private _config!: ConfigType;
|
||||
|
||||
constructor(config: ConfigType) {
|
||||
constructor (config: ConfigType) {
|
||||
super();
|
||||
this._config = this._config || config || ({} as ConfigType);
|
||||
this._config = this._config || config || {} as ConfigType;
|
||||
(this as any)[hiddenProperties] = {};
|
||||
const hook = (this as any)._hook;
|
||||
if (hook) hook.call(this);
|
||||
delete (this as any)._hook;
|
||||
}
|
||||
|
||||
public _addParent(item: TouchBarItem<any>) {
|
||||
const existing = this._parents.some((test) => test.id === item.id);
|
||||
public _addParent (item: TouchBarItem<any>) {
|
||||
const existing = this._parents.some(test => test.id === item.id);
|
||||
if (!existing) {
|
||||
this._parents.push({
|
||||
id: item.id,
|
||||
@@ -88,246 +76,211 @@ abstract class TouchBarItem<ConfigType> extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
public _removeParent(item: TouchBarItem<any>) {
|
||||
this._parents = this._parents.filter((test) => test.id !== item.id);
|
||||
public _removeParent (item: TouchBarItem<any>) {
|
||||
this._parents = this._parents.filter(test => test.id !== item.id);
|
||||
}
|
||||
}
|
||||
|
||||
class TouchBarButton
|
||||
extends TouchBarItem<Electron.TouchBarButtonConstructorOptions>
|
||||
implements Electron.TouchBarButton
|
||||
{
|
||||
class TouchBarButton extends TouchBarItem<Electron.TouchBarButtonConstructorOptions> implements Electron.TouchBarButton {
|
||||
@ImmutableProperty(() => 'button')
|
||||
type!: string;
|
||||
type!: string;
|
||||
|
||||
@LiveProperty<TouchBarButton>((config) => config.label)
|
||||
label!: string;
|
||||
@LiveProperty<TouchBarButton>(config => config.label)
|
||||
label!: string;
|
||||
|
||||
@LiveProperty<TouchBarButton>((config) => config.accessibilityLabel)
|
||||
accessibilityLabel!: string;
|
||||
@LiveProperty<TouchBarButton>(config => config.accessibilityLabel)
|
||||
accessibilityLabel!: string;
|
||||
|
||||
@LiveProperty<TouchBarButton>((config) => config.backgroundColor)
|
||||
backgroundColor!: string;
|
||||
@LiveProperty<TouchBarButton>(config => config.backgroundColor)
|
||||
backgroundColor!: string;
|
||||
|
||||
@LiveProperty<TouchBarButton>((config) => config.icon)
|
||||
icon!: Electron.NativeImage;
|
||||
@LiveProperty<TouchBarButton>(config => config.icon)
|
||||
icon!: Electron.NativeImage;
|
||||
|
||||
@LiveProperty<TouchBarButton>((config) => config.iconPosition)
|
||||
iconPosition!: Electron.TouchBarButton['iconPosition'];
|
||||
@LiveProperty<TouchBarButton>(config => config.iconPosition)
|
||||
iconPosition!: Electron.TouchBarButton['iconPosition'];
|
||||
|
||||
@LiveProperty<TouchBarButton>((config) => (typeof config.enabled !== 'boolean' ? true : config.enabled))
|
||||
enabled!: boolean;
|
||||
@LiveProperty<TouchBarButton>(config => typeof config.enabled !== 'boolean' ? true : config.enabled)
|
||||
enabled!: boolean;
|
||||
|
||||
@ImmutableProperty<TouchBarButton>(({ click: onClick }) => (typeof onClick === 'function' ? () => onClick() : null))
|
||||
onInteraction!: Function | null;
|
||||
@ImmutableProperty<TouchBarButton>(({ click: onClick }) => typeof onClick === 'function' ? () => onClick() : null)
|
||||
onInteraction!: Function | null;
|
||||
}
|
||||
|
||||
class TouchBarColorPicker
|
||||
extends TouchBarItem<Electron.TouchBarColorPickerConstructorOptions>
|
||||
implements Electron.TouchBarColorPicker
|
||||
{
|
||||
class TouchBarColorPicker extends TouchBarItem<Electron.TouchBarColorPickerConstructorOptions> implements Electron.TouchBarColorPicker {
|
||||
@ImmutableProperty(() => 'colorpicker')
|
||||
type!: string;
|
||||
type!: string;
|
||||
|
||||
@LiveProperty<TouchBarColorPicker>((config) => config.availableColors)
|
||||
availableColors!: string[];
|
||||
@LiveProperty<TouchBarColorPicker>(config => config.availableColors)
|
||||
availableColors!: string[];
|
||||
|
||||
@LiveProperty<TouchBarColorPicker>((config) => config.selectedColor)
|
||||
selectedColor!: string;
|
||||
@LiveProperty<TouchBarColorPicker>(config => config.selectedColor)
|
||||
selectedColor!: string;
|
||||
|
||||
@ImmutableProperty<TouchBarColorPicker>(({ change: onChange }, setInternalProp) =>
|
||||
typeof onChange === 'function'
|
||||
? (details: { color: string }) => {
|
||||
setInternalProp('selectedColor', details.color);
|
||||
onChange(details.color);
|
||||
}
|
||||
: null
|
||||
)
|
||||
onInteraction!: Function | null;
|
||||
@ImmutableProperty<TouchBarColorPicker>(({ change: onChange }, setInternalProp) => typeof onChange === 'function'
|
||||
? (details: { color: string }) => {
|
||||
setInternalProp('selectedColor', details.color);
|
||||
onChange(details.color);
|
||||
}
|
||||
: null)
|
||||
onInteraction!: Function | null;
|
||||
}
|
||||
|
||||
class TouchBarGroup extends TouchBarItem<Electron.TouchBarGroupConstructorOptions> implements Electron.TouchBarGroup {
|
||||
@ImmutableProperty(() => 'group')
|
||||
type!: string;
|
||||
type!: string;
|
||||
|
||||
@LiveProperty<TouchBarGroup>(
|
||||
(config) => (config.items instanceof TouchBar ? config.items : new TouchBar(config.items)),
|
||||
(self, newChild: TouchBar) => {
|
||||
if (self.child) {
|
||||
for (const item of self.child.orderedItems) {
|
||||
item._removeParent(self);
|
||||
}
|
||||
}
|
||||
for (const item of newChild.orderedItems) {
|
||||
item._addParent(self);
|
||||
@LiveProperty<TouchBarGroup>(config => config.items instanceof TouchBar ? config.items : new TouchBar(config.items), (self, newChild: TouchBar) => {
|
||||
if (self.child) {
|
||||
for (const item of self.child.orderedItems) {
|
||||
item._removeParent(self);
|
||||
}
|
||||
}
|
||||
)
|
||||
child!: TouchBar;
|
||||
for (const item of newChild.orderedItems) {
|
||||
item._addParent(self);
|
||||
}
|
||||
})
|
||||
child!: TouchBar;
|
||||
|
||||
onInteraction = null;
|
||||
}
|
||||
|
||||
class TouchBarLabel extends TouchBarItem<Electron.TouchBarLabelConstructorOptions> implements Electron.TouchBarLabel {
|
||||
@ImmutableProperty(() => 'label')
|
||||
type!: string;
|
||||
type!: string;
|
||||
|
||||
@LiveProperty<TouchBarLabel>((config) => config.label)
|
||||
label!: string;
|
||||
@LiveProperty<TouchBarLabel>(config => config.label)
|
||||
label!: string;
|
||||
|
||||
@LiveProperty<TouchBarLabel>((config) => config.accessibilityLabel)
|
||||
accessibilityLabel!: string;
|
||||
@LiveProperty<TouchBarLabel>(config => config.accessibilityLabel)
|
||||
accessibilityLabel!: string;
|
||||
|
||||
@LiveProperty<TouchBarLabel>((config) => config.textColor)
|
||||
textColor!: string;
|
||||
@LiveProperty<TouchBarLabel>(config => config.textColor)
|
||||
textColor!: string;
|
||||
|
||||
onInteraction = null;
|
||||
}
|
||||
|
||||
class TouchBarPopover
|
||||
extends TouchBarItem<Electron.TouchBarPopoverConstructorOptions>
|
||||
implements Electron.TouchBarPopover
|
||||
{
|
||||
class TouchBarPopover extends TouchBarItem<Electron.TouchBarPopoverConstructorOptions> implements Electron.TouchBarPopover {
|
||||
@ImmutableProperty(() => 'popover')
|
||||
type!: string;
|
||||
type!: string;
|
||||
|
||||
@LiveProperty<TouchBarPopover>((config) => config.label)
|
||||
label!: string;
|
||||
@LiveProperty<TouchBarPopover>(config => config.label)
|
||||
label!: string;
|
||||
|
||||
@LiveProperty<TouchBarPopover>((config) => config.icon)
|
||||
icon!: Electron.NativeImage;
|
||||
@LiveProperty<TouchBarPopover>(config => config.icon)
|
||||
icon!: Electron.NativeImage;
|
||||
|
||||
@LiveProperty<TouchBarPopover>((config) => config.showCloseButton)
|
||||
showCloseButton!: boolean;
|
||||
@LiveProperty<TouchBarPopover>(config => config.showCloseButton)
|
||||
showCloseButton!: boolean;
|
||||
|
||||
@LiveProperty<TouchBarPopover>(
|
||||
(config) => (config.items instanceof TouchBar ? config.items : new TouchBar(config.items)),
|
||||
(self, newChild: TouchBar) => {
|
||||
if (self.child) {
|
||||
for (const item of self.child.orderedItems) {
|
||||
item._removeParent(self);
|
||||
}
|
||||
}
|
||||
for (const item of newChild.orderedItems) {
|
||||
item._addParent(self);
|
||||
@LiveProperty<TouchBarPopover>(config => config.items instanceof TouchBar ? config.items : new TouchBar(config.items), (self, newChild: TouchBar) => {
|
||||
if (self.child) {
|
||||
for (const item of self.child.orderedItems) {
|
||||
item._removeParent(self);
|
||||
}
|
||||
}
|
||||
)
|
||||
child!: TouchBar;
|
||||
for (const item of newChild.orderedItems) {
|
||||
item._addParent(self);
|
||||
}
|
||||
})
|
||||
child!: TouchBar;
|
||||
|
||||
onInteraction = null;
|
||||
}
|
||||
|
||||
class TouchBarSlider
|
||||
extends TouchBarItem<Electron.TouchBarSliderConstructorOptions>
|
||||
implements Electron.TouchBarSlider
|
||||
{
|
||||
class TouchBarSlider extends TouchBarItem<Electron.TouchBarSliderConstructorOptions> implements Electron.TouchBarSlider {
|
||||
@ImmutableProperty(() => 'slider')
|
||||
type!: string;
|
||||
type!: string;
|
||||
|
||||
@LiveProperty<TouchBarSlider>((config) => config.label)
|
||||
label!: string;
|
||||
@LiveProperty<TouchBarSlider>(config => config.label)
|
||||
label!: string;
|
||||
|
||||
@LiveProperty<TouchBarSlider>((config) => config.minValue)
|
||||
minValue!: number;
|
||||
@LiveProperty<TouchBarSlider>(config => config.minValue)
|
||||
minValue!: number;
|
||||
|
||||
@LiveProperty<TouchBarSlider>((config) => config.maxValue)
|
||||
maxValue!: number;
|
||||
@LiveProperty<TouchBarSlider>(config => config.maxValue)
|
||||
maxValue!: number;
|
||||
|
||||
@LiveProperty<TouchBarSlider>((config) => config.value)
|
||||
value!: number;
|
||||
@LiveProperty<TouchBarSlider>(config => config.value)
|
||||
value!: number;
|
||||
|
||||
@ImmutableProperty<TouchBarSlider>(({ change: onChange }, setInternalProp) =>
|
||||
typeof onChange === 'function'
|
||||
? (details: { value: number }) => {
|
||||
setInternalProp('value', details.value);
|
||||
onChange(details.value);
|
||||
}
|
||||
: null
|
||||
)
|
||||
onInteraction!: Function | null;
|
||||
@ImmutableProperty<TouchBarSlider>(({ change: onChange }, setInternalProp) => typeof onChange === 'function'
|
||||
? (details: { value: number }) => {
|
||||
setInternalProp('value', details.value);
|
||||
onChange(details.value);
|
||||
}
|
||||
: null)
|
||||
onInteraction!: Function | null;
|
||||
}
|
||||
|
||||
class TouchBarSpacer
|
||||
extends TouchBarItem<Electron.TouchBarSpacerConstructorOptions>
|
||||
implements Electron.TouchBarSpacer
|
||||
{
|
||||
class TouchBarSpacer extends TouchBarItem<Electron.TouchBarSpacerConstructorOptions> implements Electron.TouchBarSpacer {
|
||||
@ImmutableProperty(() => 'spacer')
|
||||
type!: string;
|
||||
type!: string;
|
||||
|
||||
@ImmutableProperty<TouchBarSpacer>((config) => config.size)
|
||||
size!: Electron.TouchBarSpacer['size'];
|
||||
@ImmutableProperty<TouchBarSpacer>(config => config.size)
|
||||
size!: Electron.TouchBarSpacer['size'];
|
||||
|
||||
onInteraction = null;
|
||||
}
|
||||
|
||||
class TouchBarSegmentedControl
|
||||
extends TouchBarItem<Electron.TouchBarSegmentedControlConstructorOptions>
|
||||
implements Electron.TouchBarSegmentedControl
|
||||
{
|
||||
class TouchBarSegmentedControl extends TouchBarItem<Electron.TouchBarSegmentedControlConstructorOptions> implements Electron.TouchBarSegmentedControl {
|
||||
@ImmutableProperty(() => 'segmented_control')
|
||||
type!: string;
|
||||
type!: string;
|
||||
|
||||
@LiveProperty<TouchBarSegmentedControl>((config) => config.segmentStyle)
|
||||
segmentStyle!: Electron.TouchBarSegmentedControl['segmentStyle'];
|
||||
@LiveProperty<TouchBarSegmentedControl>(config => config.segmentStyle)
|
||||
segmentStyle!: Electron.TouchBarSegmentedControl['segmentStyle'];
|
||||
|
||||
@LiveProperty<TouchBarSegmentedControl>((config) => config.segments || [])
|
||||
segments!: Electron.SegmentedControlSegment[];
|
||||
@LiveProperty<TouchBarSegmentedControl>(config => config.segments || [])
|
||||
segments!: Electron.SegmentedControlSegment[];
|
||||
|
||||
@LiveProperty<TouchBarSegmentedControl>((config) => config.selectedIndex)
|
||||
selectedIndex!: number;
|
||||
@LiveProperty<TouchBarSegmentedControl>(config => config.selectedIndex)
|
||||
selectedIndex!: number;
|
||||
|
||||
@LiveProperty<TouchBarSegmentedControl>((config) => config.mode)
|
||||
mode!: Electron.TouchBarSegmentedControl['mode'];
|
||||
@LiveProperty<TouchBarSegmentedControl>(config => config.mode)
|
||||
mode!: Electron.TouchBarSegmentedControl['mode'];
|
||||
|
||||
@ImmutableProperty<TouchBarSegmentedControl>(({ change: onChange }, setInternalProp) =>
|
||||
typeof onChange === 'function'
|
||||
? (details: { selectedIndex: number; isSelected: boolean }) => {
|
||||
setInternalProp('selectedIndex', details.selectedIndex);
|
||||
onChange(details.selectedIndex, details.isSelected);
|
||||
}
|
||||
: null
|
||||
)
|
||||
onInteraction!: Function | null;
|
||||
@ImmutableProperty<TouchBarSegmentedControl>(({ change: onChange }, setInternalProp) => typeof onChange === 'function'
|
||||
? (details: { selectedIndex: number, isSelected: boolean }) => {
|
||||
setInternalProp('selectedIndex', details.selectedIndex);
|
||||
onChange(details.selectedIndex, details.isSelected);
|
||||
}
|
||||
: null)
|
||||
onInteraction!: Function | null;
|
||||
}
|
||||
|
||||
class TouchBarScrubber
|
||||
extends TouchBarItem<Electron.TouchBarScrubberConstructorOptions>
|
||||
implements Electron.TouchBarScrubber
|
||||
{
|
||||
class TouchBarScrubber extends TouchBarItem<Electron.TouchBarScrubberConstructorOptions> implements Electron.TouchBarScrubber {
|
||||
@ImmutableProperty(() => 'scrubber')
|
||||
type!: string;
|
||||
type!: string;
|
||||
|
||||
@LiveProperty<TouchBarScrubber>((config) => config.items)
|
||||
items!: Electron.ScrubberItem[];
|
||||
@LiveProperty<TouchBarScrubber>(config => config.items)
|
||||
items!: Electron.ScrubberItem[];
|
||||
|
||||
@LiveProperty<TouchBarScrubber>((config) => config.selectedStyle || null)
|
||||
selectedStyle!: Electron.TouchBarScrubber['selectedStyle'];
|
||||
@LiveProperty<TouchBarScrubber>(config => config.selectedStyle || null)
|
||||
selectedStyle!: Electron.TouchBarScrubber['selectedStyle'];
|
||||
|
||||
@LiveProperty<TouchBarScrubber>((config) => config.overlayStyle || null)
|
||||
overlayStyle!: Electron.TouchBarScrubber['overlayStyle'];
|
||||
@LiveProperty<TouchBarScrubber>(config => config.overlayStyle || null)
|
||||
overlayStyle!: Electron.TouchBarScrubber['overlayStyle'];
|
||||
|
||||
@LiveProperty<TouchBarScrubber>((config) => config.showArrowButtons || false)
|
||||
showArrowButtons!: boolean;
|
||||
@LiveProperty<TouchBarScrubber>(config => config.showArrowButtons || false)
|
||||
showArrowButtons!: boolean;
|
||||
|
||||
@LiveProperty<TouchBarScrubber>((config) => config.mode || 'free')
|
||||
mode!: Electron.TouchBarScrubber['mode'];
|
||||
@LiveProperty<TouchBarScrubber>(config => config.mode || 'free')
|
||||
mode!: Electron.TouchBarScrubber['mode'];
|
||||
|
||||
@LiveProperty<TouchBarScrubber>((config) => (typeof config.continuous === 'undefined' ? true : config.continuous))
|
||||
continuous!: boolean;
|
||||
@LiveProperty<TouchBarScrubber>(config => typeof config.continuous === 'undefined' ? true : config.continuous)
|
||||
continuous!: boolean;
|
||||
|
||||
@ImmutableProperty<TouchBarScrubber>(({ select: onSelect, highlight: onHighlight }) =>
|
||||
typeof onSelect === 'function' || typeof onHighlight === 'function'
|
||||
? (details: { type: 'select'; selectedIndex: number } | { type: 'highlight'; highlightedIndex: number }) => {
|
||||
if (details.type === 'select') {
|
||||
if (onSelect) onSelect(details.selectedIndex);
|
||||
} else {
|
||||
if (onHighlight) onHighlight(details.highlightedIndex);
|
||||
}
|
||||
@ImmutableProperty<TouchBarScrubber>(({ select: onSelect, highlight: onHighlight }) => typeof onSelect === 'function' || typeof onHighlight === 'function'
|
||||
? (details: { type: 'select'; selectedIndex: number } | { type: 'highlight'; highlightedIndex: number }) => {
|
||||
if (details.type === 'select') {
|
||||
if (onSelect) onSelect(details.selectedIndex);
|
||||
} else {
|
||||
if (onHighlight) onHighlight(details.highlightedIndex);
|
||||
}
|
||||
: null
|
||||
)
|
||||
onInteraction!: Function | null;
|
||||
}
|
||||
: null)
|
||||
onInteraction!: Function | null;
|
||||
}
|
||||
|
||||
class TouchBarOtherItemsProxy extends TouchBarItem<null> implements Electron.TouchBarOtherItemsProxy {
|
||||
@@ -339,7 +292,7 @@ const escapeItemSymbol = Symbol('escape item');
|
||||
|
||||
class TouchBar extends EventEmitter implements Electron.TouchBar {
|
||||
// Bind a touch bar to a window
|
||||
static _setOnWindow(touchBar: TouchBar | Electron.TouchBarConstructorOptions['items'], window: Electron.BaseWindow) {
|
||||
static _setOnWindow (touchBar: TouchBar | Electron.TouchBarConstructorOptions['items'], window: Electron.BaseWindow) {
|
||||
if (window._touchBar != null) {
|
||||
window._touchBar._removeFromWindow(window);
|
||||
}
|
||||
@@ -359,7 +312,7 @@ class TouchBar extends EventEmitter implements Electron.TouchBar {
|
||||
private items = new Map<string, TouchBarItem<any>>();
|
||||
orderedItems: TouchBarItem<any>[] = [];
|
||||
|
||||
constructor(options: Electron.TouchBarConstructorOptions) {
|
||||
constructor (options: Electron.TouchBarConstructorOptions) {
|
||||
super();
|
||||
|
||||
if (options == null) {
|
||||
@@ -407,7 +360,7 @@ class TouchBar extends EventEmitter implements Electron.TouchBar {
|
||||
}
|
||||
|
||||
// register in separate loop after all items are validated
|
||||
for (const item of items as TouchBarItem<any>[]) {
|
||||
for (const item of (items as TouchBarItem<any>[])) {
|
||||
this.orderedItems.push(item);
|
||||
registerItem(item);
|
||||
}
|
||||
@@ -419,7 +372,7 @@ class TouchBar extends EventEmitter implements Electron.TouchBar {
|
||||
|
||||
private [escapeItemSymbol]: TouchBarItem<unknown> | null = null;
|
||||
|
||||
set escapeItem(item: TouchBarItem<unknown> | null) {
|
||||
set escapeItem (item: TouchBarItem<unknown> | null) {
|
||||
if (item != null && !(item instanceof TouchBarItem)) {
|
||||
throw new Error('Escape item must be an instance of TouchBarItem');
|
||||
}
|
||||
@@ -434,11 +387,11 @@ class TouchBar extends EventEmitter implements Electron.TouchBar {
|
||||
this.emit('escape-item-change', item);
|
||||
}
|
||||
|
||||
get escapeItem(): TouchBarItem<unknown> | null {
|
||||
get escapeItem (): TouchBarItem<unknown> | null {
|
||||
return this[escapeItemSymbol];
|
||||
}
|
||||
|
||||
_addToWindow(window: Electron.BaseWindow) {
|
||||
_addToWindow (window: Electron.BaseWindow) {
|
||||
const { id } = window;
|
||||
|
||||
// Already added to window
|
||||
@@ -494,7 +447,7 @@ class TouchBar extends EventEmitter implements Electron.TouchBar {
|
||||
escapeItemListener(this.escapeItem);
|
||||
}
|
||||
|
||||
_removeFromWindow(window: Electron.BaseWindow) {
|
||||
_removeFromWindow (window: Electron.BaseWindow) {
|
||||
const removeListeners = this.windowListeners.get(window.id);
|
||||
if (removeListeners != null) removeListeners();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ class ForkUtilityProcess extends EventEmitter implements Electron.UtilityProcess
|
||||
#handle: ElectronInternal.UtilityProcessWrapper | null;
|
||||
#stdout: Duplex | null = null;
|
||||
#stderr: Duplex | null = null;
|
||||
constructor(modulePath: string, args?: string[], options?: Electron.ForkOptions) {
|
||||
constructor (modulePath: string, args?: string[], options?: Electron.ForkOptions) {
|
||||
super();
|
||||
|
||||
if (!modulePath) {
|
||||
@@ -53,7 +53,7 @@ class ForkUtilityProcess extends EventEmitter implements Electron.UtilityProcess
|
||||
}
|
||||
|
||||
if (typeof options.stdio === 'string') {
|
||||
const stdio: Array<'pipe' | 'ignore' | 'inherit'> = [];
|
||||
const stdio : Array<'pipe' | 'ignore' | 'inherit'> = [];
|
||||
switch (options.stdio) {
|
||||
case 'inherit':
|
||||
case 'ignore':
|
||||
@@ -119,27 +119,27 @@ class ForkUtilityProcess extends EventEmitter implements Electron.UtilityProcess
|
||||
};
|
||||
}
|
||||
|
||||
get pid() {
|
||||
get pid () {
|
||||
return this.#handle?.pid;
|
||||
}
|
||||
|
||||
get stdout() {
|
||||
get stdout () {
|
||||
return this.#stdout;
|
||||
}
|
||||
|
||||
get stderr() {
|
||||
get stderr () {
|
||||
return this.#stderr;
|
||||
}
|
||||
|
||||
postMessage(message: any, transfer?: MessagePortMain[]) {
|
||||
postMessage (message: any, transfer?: MessagePortMain[]) {
|
||||
if (Array.isArray(transfer)) {
|
||||
transfer = transfer.map((o: any) => (o instanceof MessagePortMain ? o._internalPort : o));
|
||||
transfer = transfer.map((o: any) => o instanceof MessagePortMain ? o._internalPort : o);
|
||||
return this.#handle?.postMessage(message, transfer);
|
||||
}
|
||||
return this.#handle?.postMessage(message);
|
||||
}
|
||||
|
||||
kill(): boolean {
|
||||
kill () : boolean {
|
||||
if (this.#handle === null) {
|
||||
return false;
|
||||
}
|
||||
@@ -147,6 +147,6 @@ class ForkUtilityProcess extends EventEmitter implements Electron.UtilityProcess
|
||||
}
|
||||
}
|
||||
|
||||
export function fork(modulePath: string, args?: string[], options?: Electron.ForkOptions) {
|
||||
export function fork (modulePath: string, args?: string[], options?: Electron.ForkOptions) {
|
||||
return new ForkUtilityProcess(modulePath, args, options);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
openGuestWindow,
|
||||
makeWebPreferences,
|
||||
parseContentTypeFormat
|
||||
} from '@electron/internal/browser/guest-window-manager';
|
||||
import { openGuestWindow, makeWebPreferences, parseContentTypeFormat } from '@electron/internal/browser/guest-window-manager';
|
||||
import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
|
||||
import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
|
||||
import { parseFeatures } from '@electron/internal/browser/parse-features-string';
|
||||
@@ -114,7 +110,7 @@ const paperFormats: Record<string, ElectronInternal.PageSize> = {
|
||||
// Practically, this means microns need to be > 352 microns.
|
||||
// We therefore need to verify this or it will silently fail.
|
||||
const isValidCustomPageSize = (width: number, height: number) => {
|
||||
return [width, height].every((x) => x > 352);
|
||||
return [width, height].every(x => x > 352);
|
||||
};
|
||||
|
||||
// JavaScript implementations of WebContents.
|
||||
@@ -134,10 +130,10 @@ WebContents.prototype._sendInternal = function (channel, ...args) {
|
||||
return this.mainFrame._sendInternal(channel, ...args);
|
||||
};
|
||||
|
||||
function getWebFrame(contents: Electron.WebContents, frame: number | [number, number]) {
|
||||
function getWebFrame (contents: Electron.WebContents, frame: number | [number, number]) {
|
||||
if (typeof frame === 'number') {
|
||||
return webFrameMain.fromId(contents.mainFrame.processId, frame);
|
||||
} else if (Array.isArray(frame) && frame.length === 2 && frame.every((value) => typeof value === 'number')) {
|
||||
} else if (Array.isArray(frame) && frame.length === 2 && frame.every(value => typeof value === 'number')) {
|
||||
return webFrameMain.fromId(frame[0], frame[1]);
|
||||
} else {
|
||||
throw new Error('Missing required frame argument (must be number or [processId, frameId])');
|
||||
@@ -152,12 +148,12 @@ WebContents.prototype.sendToFrame = function (frameId, channel, ...args) {
|
||||
};
|
||||
|
||||
// Following methods are mapped to webFrame.
|
||||
const webFrameMethods = ['insertCSS', 'insertText', 'removeInsertedCSS', 'setVisualZoomLevelLimits'] as (
|
||||
| 'insertCSS'
|
||||
| 'insertText'
|
||||
| 'removeInsertedCSS'
|
||||
| 'setVisualZoomLevelLimits'
|
||||
)[];
|
||||
const webFrameMethods = [
|
||||
'insertCSS',
|
||||
'insertText',
|
||||
'removeInsertedCSS',
|
||||
'setVisualZoomLevelLimits'
|
||||
] as ('insertCSS' | 'insertText' | 'removeInsertedCSS' | 'setVisualZoomLevelLimits')[];
|
||||
|
||||
for (const method of webFrameMethods) {
|
||||
WebContents.prototype[method] = function (...args: any[]): Promise<any> {
|
||||
@@ -179,27 +175,14 @@ const waitTillCanExecuteJavaScript = async (webContents: Electron.WebContents) =
|
||||
// WebContents has been loaded.
|
||||
WebContents.prototype.executeJavaScript = async function (code, hasUserGesture) {
|
||||
await waitTillCanExecuteJavaScript(this);
|
||||
return ipcMainUtils.invokeInWebContents(
|
||||
this,
|
||||
IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD,
|
||||
'executeJavaScript',
|
||||
String(code),
|
||||
!!hasUserGesture
|
||||
);
|
||||
return ipcMainUtils.invokeInWebContents(this, IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD, 'executeJavaScript', String(code), !!hasUserGesture);
|
||||
};
|
||||
WebContents.prototype.executeJavaScriptInIsolatedWorld = async function (worldId, code, hasUserGesture) {
|
||||
await waitTillCanExecuteJavaScript(this);
|
||||
return ipcMainUtils.invokeInWebContents(
|
||||
this,
|
||||
IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD,
|
||||
'executeJavaScriptInIsolatedWorld',
|
||||
worldId,
|
||||
code,
|
||||
!!hasUserGesture
|
||||
);
|
||||
return ipcMainUtils.invokeInWebContents(this, IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD, 'executeJavaScriptInIsolatedWorld', worldId, code, !!hasUserGesture);
|
||||
};
|
||||
|
||||
function checkType<T>(value: T, type: 'number' | 'boolean' | 'string' | 'object', name: string): T {
|
||||
function checkType<T> (value: T, type: 'number' | 'boolean' | 'string' | 'object', name: string): T {
|
||||
// eslint-disable-next-line valid-typeof
|
||||
if (typeof value !== type) {
|
||||
throw new TypeError(`${name} must be a ${type}`);
|
||||
@@ -208,7 +191,7 @@ function checkType<T>(value: T, type: 'number' | 'boolean' | 'string' | 'object'
|
||||
return value;
|
||||
}
|
||||
|
||||
function parsePageSize(pageSize: string | ElectronInternal.PageSize) {
|
||||
function parsePageSize (pageSize: string | ElectronInternal.PageSize) {
|
||||
if (typeof pageSize === 'string') {
|
||||
const format = paperFormats[pageSize.toLowerCase()];
|
||||
if (!format) {
|
||||
@@ -235,8 +218,8 @@ WebContents.prototype.printToPDF = async function (options) {
|
||||
const pageSize = parsePageSize(options.pageSize ?? 'letter');
|
||||
|
||||
const { top, bottom, left, right } = margins;
|
||||
const validHeight = [top, bottom].every((u) => u === undefined || u <= pageSize.paperHeight);
|
||||
const validWidth = [left, right].every((u) => u === undefined || u <= pageSize.paperWidth);
|
||||
const validHeight = [top, bottom].every(u => u === undefined || u <= pageSize.paperHeight);
|
||||
const validWidth = [left, right].every(u => u === undefined || u <= pageSize.paperWidth);
|
||||
|
||||
if (!validHeight || !validWidth) {
|
||||
throw new Error('margins must be less than or equal to pageSize');
|
||||
@@ -349,21 +332,19 @@ WebContents.prototype.loadFile = function (filePath, options = {}) {
|
||||
}
|
||||
const { query, search, hash } = options;
|
||||
|
||||
return this.loadURL(
|
||||
url.format({
|
||||
protocol: 'file',
|
||||
slashes: true,
|
||||
pathname: path.resolve(app.getAppPath(), filePath),
|
||||
query,
|
||||
search,
|
||||
hash
|
||||
})
|
||||
);
|
||||
return this.loadURL(url.format({
|
||||
protocol: 'file',
|
||||
slashes: true,
|
||||
pathname: path.resolve(app.getAppPath(), filePath),
|
||||
query,
|
||||
search,
|
||||
hash
|
||||
}));
|
||||
};
|
||||
|
||||
type LoadError = { errorCode: number; errorDescription: string; url: string };
|
||||
type LoadError = { errorCode: number, errorDescription: string, url: string };
|
||||
|
||||
function _awaitNextLoad(this: Electron.WebContents, navigationUrl: string) {
|
||||
function _awaitNextLoad (this: Electron.WebContents, navigationUrl: string) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const resolveAndCleanup = () => {
|
||||
removeListeners();
|
||||
@@ -371,9 +352,7 @@ function _awaitNextLoad(this: Electron.WebContents, navigationUrl: string) {
|
||||
};
|
||||
let error: LoadError | undefined;
|
||||
const rejectAndCleanup = ({ errorCode, errorDescription, url }: LoadError) => {
|
||||
const err = new Error(
|
||||
`${errorDescription} (${errorCode}) loading '${typeof url === 'string' ? url.substr(0, 2048) : url}'`
|
||||
);
|
||||
const err = new Error(`${errorDescription} (${errorCode}) loading '${typeof url === 'string' ? url.substr(0, 2048) : url}'`);
|
||||
Object.assign(err, { errno: errorCode, code: errorDescription, url });
|
||||
removeListeners();
|
||||
reject(err);
|
||||
@@ -406,13 +385,7 @@ function _awaitNextLoad(this: Electron.WebContents, navigationUrl: string) {
|
||||
navigationStarted = true;
|
||||
}
|
||||
};
|
||||
const failListener = (
|
||||
event: Electron.Event,
|
||||
errorCode: number,
|
||||
errorDescription: string,
|
||||
validatedURL: string,
|
||||
isMainFrame: boolean
|
||||
) => {
|
||||
const failListener = (event: Electron.Event, errorCode: number, errorDescription: string, validatedURL: string, isMainFrame: boolean) => {
|
||||
if (!error && isMainFrame) {
|
||||
error = { errorCode, errorDescription, url: validatedURL };
|
||||
}
|
||||
@@ -454,7 +427,7 @@ function _awaitNextLoad(this: Electron.WebContents, navigationUrl: string) {
|
||||
this.on('did-stop-loading', stopLoadingListener);
|
||||
this.on('destroyed', stopLoadingListener);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
WebContents.prototype.loadURL = function (url, options) {
|
||||
const p = _awaitNextLoad.call(this, url);
|
||||
@@ -472,20 +445,11 @@ WebContents.prototype.saveVideoFrameAs = function (x: number, y: number) {
|
||||
this.mainFrame.saveVideoFrameAs(x, y);
|
||||
};
|
||||
|
||||
WebContents.prototype.setWindowOpenHandler = function (
|
||||
handler: (details: Electron.HandlerDetails) => Electron.WindowOpenHandlerResponse
|
||||
) {
|
||||
WebContents.prototype.setWindowOpenHandler = function (handler: (details: Electron.HandlerDetails) => Electron.WindowOpenHandlerResponse) {
|
||||
this._windowOpenHandler = handler;
|
||||
};
|
||||
|
||||
WebContents.prototype._callWindowOpenHandler = function (
|
||||
event: Electron.Event,
|
||||
details: Electron.HandlerDetails
|
||||
): {
|
||||
browserWindowConstructorOptions: BrowserWindowConstructorOptions | null;
|
||||
outlivesOpener: boolean;
|
||||
createWindow?: Electron.CreateWindowFunction;
|
||||
} {
|
||||
WebContents.prototype._callWindowOpenHandler = function (event: Electron.Event, details: Electron.HandlerDetails): {browserWindowConstructorOptions: BrowserWindowConstructorOptions | null, outlivesOpener: boolean, createWindow?: Electron.CreateWindowFunction} {
|
||||
const defaultResponse = {
|
||||
browserWindowConstructorOptions: null,
|
||||
outlivesOpener: false,
|
||||
@@ -514,14 +478,13 @@ WebContents.prototype._callWindowOpenHandler = function (
|
||||
return defaultResponse;
|
||||
} else if (response.action === 'allow') {
|
||||
return {
|
||||
browserWindowConstructorOptions:
|
||||
typeof response.overrideBrowserWindowOptions === 'object' ? response.overrideBrowserWindowOptions : null,
|
||||
browserWindowConstructorOptions: typeof response.overrideBrowserWindowOptions === 'object' ? response.overrideBrowserWindowOptions : null,
|
||||
outlivesOpener: typeof response.outlivesOpener === 'boolean' ? response.outlivesOpener : false,
|
||||
createWindow: typeof response.createWindow === 'function' ? response.createWindow : undefined
|
||||
};
|
||||
} else {
|
||||
event.preventDefault();
|
||||
console.error("The window open handler response must be an object with an 'action' property of 'allow' or 'deny'.");
|
||||
console.error('The window open handler response must be an object with an \'action\' property of \'allow\' or \'deny\'.');
|
||||
return defaultResponse;
|
||||
}
|
||||
};
|
||||
@@ -539,19 +502,13 @@ WebContents.prototype.canGoBack = function () {
|
||||
return this._canGoBack();
|
||||
};
|
||||
|
||||
const canGoForwardDeprecated = deprecate.warnOnce(
|
||||
'webContents.canGoForward',
|
||||
'webContents.navigationHistory.canGoForward'
|
||||
);
|
||||
const canGoForwardDeprecated = deprecate.warnOnce('webContents.canGoForward', 'webContents.navigationHistory.canGoForward');
|
||||
WebContents.prototype.canGoForward = function () {
|
||||
canGoForwardDeprecated();
|
||||
return this._canGoForward();
|
||||
};
|
||||
|
||||
const canGoToOffsetDeprecated = deprecate.warnOnce(
|
||||
'webContents.canGoToOffset',
|
||||
'webContents.navigationHistory.canGoToOffset'
|
||||
);
|
||||
const canGoToOffsetDeprecated = deprecate.warnOnce('webContents.canGoToOffset', 'webContents.navigationHistory.canGoToOffset');
|
||||
WebContents.prototype.canGoToOffset = function (index: number) {
|
||||
canGoToOffsetDeprecated();
|
||||
return this._canGoToOffset(index);
|
||||
@@ -587,17 +544,13 @@ WebContents.prototype.goToOffset = function (index: number) {
|
||||
return this._goToOffset(index);
|
||||
};
|
||||
|
||||
const consoleMessageDeprecated = deprecate.warnOnceMessage(
|
||||
"'console-message' arguments are deprecated and will be removed. Please use Event<WebContentsConsoleMessageEventParams> object instead."
|
||||
);
|
||||
const consoleMessageDeprecated = deprecate.warnOnceMessage('\'console-message\' arguments are deprecated and will be removed. Please use Event<WebContentsConsoleMessageEventParams> object instead.');
|
||||
|
||||
// Add JavaScript wrappers for WebContents class.
|
||||
WebContents.prototype._init = function () {
|
||||
const prefs = this.getLastWebPreferences() || {};
|
||||
if (!prefs.nodeIntegration && prefs.preload != null && prefs.sandbox == null) {
|
||||
deprecate.log(
|
||||
"The default sandbox option for windows without nodeIntegration is changing. Presently, by default, when a window has a preload script, it defaults to being unsandboxed. In Electron 20, this default will be changing, and all windows that have nodeIntegration: false (which is the default) will be sandboxed by default. If your preload script doesn't use Node, no action is needed. If your preload script does use Node, either refactor it to move Node usage to the main process, or specify sandbox: false in your WebPreferences."
|
||||
);
|
||||
deprecate.log('The default sandbox option for windows without nodeIntegration is changing. Presently, by default, when a window has a preload script, it defaults to being unsandboxed. In Electron 20, this default will be changing, and all windows that have nodeIntegration: false (which is the default) will be sandboxed by default. If your preload script doesn\'t use Node, no action is needed. If your preload script does use Node, either refactor it to move Node usage to the main process, or specify sandbox: false in your WebPreferences.');
|
||||
}
|
||||
// Read off the ID at construction time, so that it's accessible even after
|
||||
// the underlying C++ WebContents is destroyed.
|
||||
@@ -611,9 +564,7 @@ WebContents.prototype._init = function () {
|
||||
|
||||
const ipc = new IpcMainImpl();
|
||||
Object.defineProperty(this, 'ipc', {
|
||||
get() {
|
||||
return ipc;
|
||||
},
|
||||
get () { return ipc; },
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
@@ -634,15 +585,13 @@ WebContents.prototype._init = function () {
|
||||
getEntryAtIndex: this._getNavigationEntryAtIndex.bind(this),
|
||||
removeEntryAtIndex: this._removeNavigationEntryAtIndex.bind(this),
|
||||
getAllEntries: this._getHistory.bind(this),
|
||||
restore: ({ index, entries }: { index?: number; entries: NavigationEntry[] }) => {
|
||||
restore: ({ index, entries }: { index?: number, entries: NavigationEntry[] }) => {
|
||||
if (index === undefined) {
|
||||
index = entries.length - 1;
|
||||
}
|
||||
|
||||
if (index < 0 || !entries[index]) {
|
||||
throw new Error(
|
||||
'Invalid index. Index must be a positive integer and within the bounds of the entries length.'
|
||||
);
|
||||
throw new Error('Invalid index. Index must be a positive integer and within the bounds of the entries length.');
|
||||
}
|
||||
|
||||
const p = _awaitNextLoad.call(this, entries[index].url);
|
||||
@@ -666,9 +615,7 @@ WebContents.prototype._init = function () {
|
||||
|
||||
// Log out a hint to help users better debug renderer crashes.
|
||||
if (loggingEnabled()) {
|
||||
console.info(
|
||||
`Renderer process ${details.reason} - see https://www.electronjs.org/docs/tutorial/application-debugging for potential debugging information.`
|
||||
);
|
||||
console.info(`Renderer process ${details.reason} - see https://www.electronjs.org/docs/tutorial/application-debugging for potential debugging information.`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -678,9 +625,7 @@ WebContents.prototype._init = function () {
|
||||
// All other types should ignore the "proceed" signal and unload
|
||||
// regardless.
|
||||
if (type === 'window' || type === 'offscreen' || type === 'browserView') {
|
||||
if (!proceed) {
|
||||
return event.preventDefault();
|
||||
}
|
||||
if (!proceed) { return event.preventDefault(); }
|
||||
}
|
||||
});
|
||||
|
||||
@@ -765,8 +710,8 @@ WebContents.prototype._init = function () {
|
||||
const secureOverrideWebPreferences = windowOpenOverriddenOptions
|
||||
? {
|
||||
// Allow setting of backgroundColor as a webPreference even though
|
||||
// it's technically a BrowserWindowConstructorOptions option because
|
||||
// we need to access it in the renderer at init time.
|
||||
// it's technically a BrowserWindowConstructorOptions option because
|
||||
// we need to access it in the renderer at init time.
|
||||
backgroundColor: windowOpenOverriddenOptions.backgroundColor,
|
||||
transparent: windowOpenOverriddenOptions.transparent,
|
||||
...windowOpenOverriddenOptions.webPreferences
|
||||
@@ -787,54 +732,38 @@ WebContents.prototype._init = function () {
|
||||
});
|
||||
|
||||
// Create a new browser window for "window.open"
|
||||
this.on(
|
||||
'-add-new-contents',
|
||||
(
|
||||
event,
|
||||
webContents,
|
||||
disposition,
|
||||
_userGesture,
|
||||
_left,
|
||||
_top,
|
||||
_width,
|
||||
_height,
|
||||
url,
|
||||
frameName,
|
||||
referrer,
|
||||
rawFeatures,
|
||||
postData
|
||||
) => {
|
||||
const overriddenOptions = windowOpenOverriddenOptions || undefined;
|
||||
const outlivesOpener = windowOpenOutlivesOpenerOption;
|
||||
const windowOpenFunction = createWindow;
|
||||
this.on('-add-new-contents', (event, webContents, disposition, _userGesture, _left, _top, _width, _height, url, frameName, referrer, rawFeatures, postData) => {
|
||||
const overriddenOptions = windowOpenOverriddenOptions || undefined;
|
||||
const outlivesOpener = windowOpenOutlivesOpenerOption;
|
||||
const windowOpenFunction = createWindow;
|
||||
|
||||
createWindow = undefined;
|
||||
windowOpenOverriddenOptions = null;
|
||||
// false is the default
|
||||
windowOpenOutlivesOpenerOption = false;
|
||||
createWindow = undefined;
|
||||
windowOpenOverriddenOptions = null;
|
||||
// false is the default
|
||||
windowOpenOutlivesOpenerOption = false;
|
||||
|
||||
if (disposition !== 'foreground-tab' && disposition !== 'new-window' && disposition !== 'background-tab') {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
openGuestWindow({
|
||||
embedder: this,
|
||||
guest: webContents,
|
||||
overrideBrowserWindowOptions: overriddenOptions,
|
||||
disposition,
|
||||
referrer,
|
||||
postData,
|
||||
windowOpenArgs: {
|
||||
url,
|
||||
frameName,
|
||||
features: rawFeatures
|
||||
},
|
||||
outlivesOpener,
|
||||
createWindow: windowOpenFunction
|
||||
});
|
||||
if ((disposition !== 'foreground-tab' && disposition !== 'new-window' &&
|
||||
disposition !== 'background-tab')) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
openGuestWindow({
|
||||
embedder: this,
|
||||
guest: webContents,
|
||||
overrideBrowserWindowOptions: overriddenOptions,
|
||||
disposition,
|
||||
referrer,
|
||||
postData,
|
||||
windowOpenArgs: {
|
||||
url,
|
||||
frameName,
|
||||
features: rawFeatures
|
||||
},
|
||||
outlivesOpener,
|
||||
createWindow: windowOpenFunction
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.on('login', (event, ...args) => {
|
||||
@@ -873,17 +802,14 @@ WebContents.prototype._init = function () {
|
||||
originCounts.set(origin, (originCounts.get(origin) ?? 0) + 1);
|
||||
|
||||
// TODO: translate?
|
||||
const checkbox =
|
||||
originCounts.get(origin)! > 1 && prefs.safeDialogs
|
||||
? prefs.safeDialogsMessage || 'Prevent this app from creating additional dialogs'
|
||||
: '';
|
||||
const checkbox = originCounts.get(origin)! > 1 && prefs.safeDialogs ? prefs.safeDialogsMessage || 'Prevent this app from creating additional dialogs' : '';
|
||||
const parent = this.getOwnerBrowserWindow();
|
||||
const abortController = new AbortController();
|
||||
const options: MessageBoxOptions = {
|
||||
message: info.messageText,
|
||||
checkboxLabel: checkbox,
|
||||
signal: abortController.signal,
|
||||
...(info.dialogType === 'confirm'
|
||||
...(info.dialogType === 'confirm')
|
||||
? {
|
||||
buttons: ['OK', 'Cancel'],
|
||||
defaultId: 0,
|
||||
@@ -893,11 +819,10 @@ WebContents.prototype._init = function () {
|
||||
buttons: ['OK'],
|
||||
defaultId: -1, // No default button
|
||||
cancelId: 0
|
||||
})
|
||||
}
|
||||
};
|
||||
openDialogs.add(abortController);
|
||||
const promise =
|
||||
parent && !prefs.offscreen ? dialog.showMessageBox(parent, options) : dialog.showMessageBox(options);
|
||||
const promise = parent && !prefs.offscreen ? dialog.showMessageBox(parent, options) : dialog.showMessageBox(options);
|
||||
try {
|
||||
const result = await promise;
|
||||
if (abortController.signal.aborted || this.isDestroyed()) return;
|
||||
@@ -909,15 +834,13 @@ WebContents.prototype._init = function () {
|
||||
});
|
||||
|
||||
this.on('-cancel-dialogs', () => {
|
||||
for (const controller of openDialogs) {
|
||||
controller.abort();
|
||||
}
|
||||
for (const controller of openDialogs) { controller.abort(); }
|
||||
openDialogs.clear();
|
||||
});
|
||||
|
||||
// TODO(samuelmaddock): remove deprecated 'console-message' arguments
|
||||
this.on('-console-message' as any, (event: Electron.Event<Electron.WebContentsConsoleMessageEventParams>) => {
|
||||
const hasDeprecatedListener = this.listeners('console-message').some((listener) => listener.length > 1);
|
||||
const hasDeprecatedListener = this.listeners('console-message').some(listener => listener.length > 1);
|
||||
if (hasDeprecatedListener) {
|
||||
consoleMessageDeprecated();
|
||||
}
|
||||
@@ -931,17 +854,7 @@ WebContents.prototype._init = function () {
|
||||
}
|
||||
});
|
||||
|
||||
app.emit(
|
||||
'web-contents-created',
|
||||
{
|
||||
sender: this,
|
||||
preventDefault() {},
|
||||
get defaultPrevented() {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
this
|
||||
);
|
||||
app.emit('web-contents-created', { sender: this, preventDefault () {}, get defaultPrevented () { return false; } }, this);
|
||||
|
||||
// Properties
|
||||
|
||||
@@ -977,23 +890,23 @@ WebContents.prototype._init = function () {
|
||||
};
|
||||
|
||||
// Public APIs.
|
||||
export function create(options = {}): Electron.WebContents {
|
||||
export function create (options = {}): Electron.WebContents {
|
||||
return new (WebContents as any)(options);
|
||||
}
|
||||
|
||||
export function fromId(id: number) {
|
||||
export function fromId (id: number) {
|
||||
return binding.fromId(id);
|
||||
}
|
||||
|
||||
export function fromFrame(frame: Electron.WebFrameMain) {
|
||||
export function fromFrame (frame: Electron.WebFrameMain) {
|
||||
return binding.fromFrame(frame);
|
||||
}
|
||||
|
||||
export function fromDevToolsTargetId(targetId: string) {
|
||||
export function fromDevToolsTargetId (targetId: string) {
|
||||
return binding.fromDevToolsTargetId(targetId);
|
||||
}
|
||||
|
||||
export function getFocusedWebContents() {
|
||||
export function getFocusedWebContents () {
|
||||
let focused = null;
|
||||
for (const contents of binding.getAllWebContents()) {
|
||||
if (!contents.isFocused()) continue;
|
||||
@@ -1004,6 +917,6 @@ export function getFocusedWebContents() {
|
||||
}
|
||||
return focused;
|
||||
}
|
||||
export function getAllWebContents() {
|
||||
export function getAllWebContents () {
|
||||
return binding.getAllWebContents();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { MessagePortMain } from '@electron/internal/browser/message-port-main';
|
||||
const { WebFrameMain, fromId, fromFrameToken } = process._linkedBinding('electron_browser_web_frame_main');
|
||||
|
||||
Object.defineProperty(WebFrameMain.prototype, 'ipc', {
|
||||
get() {
|
||||
get () {
|
||||
const ipc = new IpcMainImpl();
|
||||
Object.defineProperty(this, 'ipc', { value: ipc });
|
||||
return ipc;
|
||||
@@ -37,7 +37,7 @@ WebFrameMain.prototype._sendInternal = function (channel, ...args) {
|
||||
|
||||
WebFrameMain.prototype.postMessage = function (...args) {
|
||||
if (Array.isArray(args[2])) {
|
||||
args[2] = args[2].map((o) => (o instanceof MessagePortMain ? o._internalPort : o));
|
||||
args[2] = args[2].map(o => o instanceof MessagePortMain ? o._internalPort : o);
|
||||
}
|
||||
this._postMessage(...args);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { shell } from 'electron/common';
|
||||
import { app, Menu } from 'electron/main';
|
||||
import { Menu } from 'electron/main';
|
||||
|
||||
const isMac = process.platform === 'darwin';
|
||||
|
||||
@@ -12,47 +11,13 @@ export const setApplicationMenuWasSet = () => {
|
||||
export const setDefaultApplicationMenu = () => {
|
||||
if (applicationMenuWasSet) return;
|
||||
|
||||
const helpMenu: Electron.MenuItemConstructorOptions = {
|
||||
role: 'help',
|
||||
submenu: app.isPackaged
|
||||
? []
|
||||
: [
|
||||
{
|
||||
label: 'Learn More',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://electronjs.org');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Documentation',
|
||||
click: async () => {
|
||||
const version = process.versions.electron;
|
||||
await shell.openExternal(`https://github.com/electron/electron/tree/v${version}/docs#readme`);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Community Discussions',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://discord.gg/electronjs');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Search Issues',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://github.com/electron/electron/issues');
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const macAppMenu: Electron.MenuItemConstructorOptions = { role: 'appMenu' };
|
||||
const template: Electron.MenuItemConstructorOptions[] = [
|
||||
...(isMac ? [macAppMenu] : []),
|
||||
{ role: 'fileMenu' },
|
||||
{ role: 'editMenu' },
|
||||
{ role: 'viewMenu' },
|
||||
{ role: 'windowMenu' },
|
||||
helpMenu
|
||||
{ role: 'windowMenu' }
|
||||
];
|
||||
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
|
||||
@@ -8,30 +8,29 @@ import * as fs from 'fs';
|
||||
|
||||
const convertToMenuTemplate = function (items: ContextMenuItem[], handler: (id: number) => void) {
|
||||
return items.map(function (item) {
|
||||
const transformed: Electron.MenuItemConstructorOptions =
|
||||
item.type === 'subMenu'
|
||||
const transformed: Electron.MenuItemConstructorOptions = item.type === 'subMenu'
|
||||
? {
|
||||
type: 'submenu',
|
||||
label: item.label,
|
||||
enabled: item.enabled,
|
||||
submenu: convertToMenuTemplate(item.subItems, handler)
|
||||
}
|
||||
: item.type === 'separator'
|
||||
? {
|
||||
type: 'submenu',
|
||||
label: item.label,
|
||||
enabled: item.enabled,
|
||||
submenu: convertToMenuTemplate(item.subItems, handler)
|
||||
type: 'separator'
|
||||
}
|
||||
: item.type === 'separator'
|
||||
: item.type === 'checkbox'
|
||||
? {
|
||||
type: 'separator'
|
||||
type: 'checkbox',
|
||||
label: item.label,
|
||||
enabled: item.enabled,
|
||||
checked: item.checked
|
||||
}
|
||||
: item.type === 'checkbox'
|
||||
? {
|
||||
type: 'checkbox',
|
||||
label: item.label,
|
||||
enabled: item.enabled,
|
||||
checked: item.checked
|
||||
}
|
||||
: {
|
||||
type: 'normal',
|
||||
label: item.label,
|
||||
enabled: item.enabled
|
||||
};
|
||||
: {
|
||||
type: 'normal',
|
||||
label: item.label,
|
||||
enabled: item.enabled
|
||||
};
|
||||
|
||||
if (item.id != null) {
|
||||
transformed.click = () => handler(item.id);
|
||||
@@ -68,21 +67,18 @@ const assertChromeDevTools = function (contents: Electron.WebContents, api: stri
|
||||
}
|
||||
};
|
||||
|
||||
ipcMainInternal.handle(
|
||||
IPC_MESSAGES.INSPECTOR_CONTEXT_MENU,
|
||||
function (event, items: ContextMenuItem[], isEditMenu: boolean) {
|
||||
return new Promise<number | void>((resolve) => {
|
||||
if (event.type !== 'frame') return;
|
||||
assertChromeDevTools(event.sender, 'window.InspectorFrontendHost.showContextMenuAtPoint()');
|
||||
ipcMainInternal.handle(IPC_MESSAGES.INSPECTOR_CONTEXT_MENU, function (event, items: ContextMenuItem[], isEditMenu: boolean) {
|
||||
return new Promise<number | void>(resolve => {
|
||||
if (event.type !== 'frame') return;
|
||||
assertChromeDevTools(event.sender, 'window.InspectorFrontendHost.showContextMenuAtPoint()');
|
||||
|
||||
const template = isEditMenu ? getEditMenuItems() : convertToMenuTemplate(items, resolve);
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
const window = event.sender.getOwnerBrowserWindow()!;
|
||||
const template = isEditMenu ? getEditMenuItems() : convertToMenuTemplate(items, resolve);
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
const window = event.sender.getOwnerBrowserWindow()!;
|
||||
|
||||
menu.popup({ window, callback: () => resolve() });
|
||||
});
|
||||
}
|
||||
);
|
||||
menu.popup({ window, callback: () => resolve() });
|
||||
});
|
||||
});
|
||||
|
||||
ipcMainInternal.handle(IPC_MESSAGES.INSPECTOR_SELECT_FILE, async function (event) {
|
||||
if (event.type !== 'frame') return [];
|
||||
@@ -97,20 +93,17 @@ ipcMainInternal.handle(IPC_MESSAGES.INSPECTOR_SELECT_FILE, async function (event
|
||||
return [path, data];
|
||||
});
|
||||
|
||||
ipcMainUtils.handleSync(
|
||||
IPC_MESSAGES.INSPECTOR_CONFIRM,
|
||||
async function (event, message: string = '', title: string = '') {
|
||||
if (event.type !== 'frame') return;
|
||||
assertChromeDevTools(event.sender, 'window.confirm()');
|
||||
ipcMainUtils.handleSync(IPC_MESSAGES.INSPECTOR_CONFIRM, async function (event, message: string = '', title: string = '') {
|
||||
if (event.type !== 'frame') return;
|
||||
assertChromeDevTools(event.sender, 'window.confirm()');
|
||||
|
||||
const options = {
|
||||
message: String(message),
|
||||
title: String(title),
|
||||
buttons: ['OK', 'Cancel'],
|
||||
cancelId: 1
|
||||
};
|
||||
const window = event.sender.getOwnerBrowserWindow()!;
|
||||
const { response } = await dialog.showMessageBox(window, options);
|
||||
return response === 0;
|
||||
}
|
||||
);
|
||||
const options = {
|
||||
message: String(message),
|
||||
title: String(title),
|
||||
buttons: ['OK', 'Cancel'],
|
||||
cancelId: 1
|
||||
};
|
||||
const window = event.sender.getOwnerBrowserWindow()!;
|
||||
const { response } = await dialog.showMessageBox(window, options);
|
||||
return response === 0;
|
||||
});
|
||||
|
||||
@@ -3,12 +3,7 @@ import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-util
|
||||
import { parseWebViewWebPreferences } from '@electron/internal/browser/parse-features-string';
|
||||
import { webViewEvents } from '@electron/internal/browser/web-view-events';
|
||||
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
|
||||
import {
|
||||
syncMethods,
|
||||
asyncMethods,
|
||||
properties,
|
||||
navigationHistorySyncMethods
|
||||
} from '@electron/internal/common/web-view-methods';
|
||||
import { syncMethods, asyncMethods, properties, navigationHistorySyncMethods } from '@electron/internal/common/web-view-methods';
|
||||
|
||||
import { webContents } from 'electron/main';
|
||||
|
||||
@@ -27,11 +22,13 @@ const supportedWebViewEvents = Object.keys(webViewEvents);
|
||||
const guestInstances = new Map<number, GuestInstance>();
|
||||
const embedderElementsMap = new Map<string, number>();
|
||||
|
||||
function makeWebPreferences(embedder: Electron.WebContents, params: Record<string, any>) {
|
||||
function makeWebPreferences (embedder: Electron.WebContents, params: Record<string, any>) {
|
||||
// parse the 'webpreferences' attribute string, if set
|
||||
// this uses the same parsing rules as window.open uses for its features
|
||||
const parsedWebPreferences =
|
||||
typeof params.webpreferences === 'string' ? parseWebViewWebPreferences(params.webpreferences) : null;
|
||||
typeof params.webpreferences === 'string'
|
||||
? parseWebViewWebPreferences(params.webpreferences)
|
||||
: null;
|
||||
|
||||
const webPreferences: Electron.WebPreferences = {
|
||||
nodeIntegration: params.nodeintegration ?? false,
|
||||
@@ -71,7 +68,7 @@ function makeWebPreferences(embedder: Electron.WebContents, params: Record<strin
|
||||
return webPreferences;
|
||||
}
|
||||
|
||||
function makeLoadURLOptions(params: Record<string, any>) {
|
||||
function makeLoadURLOptions (params: Record<string, any>) {
|
||||
const opts: Electron.LoadURLOptions = {};
|
||||
if (params.httpreferrer) {
|
||||
opts.httpReferrer = params.httpreferrer;
|
||||
@@ -83,16 +80,11 @@ function makeLoadURLOptions(params: Record<string, any>) {
|
||||
}
|
||||
|
||||
// Create a new guest instance.
|
||||
const createGuest = function (
|
||||
embedder: Electron.WebContents,
|
||||
embedderFrameToken: string,
|
||||
elementInstanceId: number,
|
||||
params: Record<string, any>
|
||||
) {
|
||||
const createGuest = function (embedder: Electron.WebContents, embedderFrameToken: string, elementInstanceId: number, params: Record<string, any>) {
|
||||
const webPreferences = makeWebPreferences(embedder, params);
|
||||
const event = {
|
||||
sender: embedder,
|
||||
preventDefault() {
|
||||
preventDefault () {
|
||||
this.defaultPrevented = true;
|
||||
},
|
||||
defaultPrevented: false
|
||||
@@ -274,18 +266,13 @@ const isWebViewTagEnabled = function (contents: Electron.WebContents) {
|
||||
return isWebViewTagEnabledCache.get(contents);
|
||||
};
|
||||
|
||||
const makeSafeHandler = function <Event extends { sender: Electron.WebContents }>(
|
||||
channel: string,
|
||||
handler: (event: Event, ...args: any[]) => any
|
||||
) {
|
||||
const makeSafeHandler = function<Event extends { sender: Electron.WebContents }> (channel: string, handler: (event: Event, ...args: any[]) => any) {
|
||||
return (event: Electron.IpcMainInvokeEvent | Electron.IpcMainServiceWorkerInvokeEvent, ...args: any[]) => {
|
||||
if (event.type !== 'frame') return;
|
||||
if (isWebViewTagEnabled(event.sender)) {
|
||||
return handler(event as unknown as Event, ...args);
|
||||
} else {
|
||||
console.error(
|
||||
`<webview> IPC message ${channel} sent by WebContents with <webview> disabled (${event.sender.id})`
|
||||
);
|
||||
console.error(`<webview> IPC message ${channel} sent by WebContents with <webview> disabled (${event.sender.id})`);
|
||||
throw new Error('<webview> disabled');
|
||||
}
|
||||
};
|
||||
@@ -295,19 +282,13 @@ const handleMessage = function (channel: string, handler: (event: Electron.IpcMa
|
||||
ipcMainInternal.handle(channel, makeSafeHandler(channel, handler));
|
||||
};
|
||||
|
||||
const handleMessageSync = function (
|
||||
channel: string,
|
||||
handler: (event: { sender: Electron.WebContents }, ...args: any[]) => any
|
||||
) {
|
||||
const handleMessageSync = function (channel: string, handler: (event: { sender: Electron.WebContents }, ...args: any[]) => any) {
|
||||
ipcMainUtils.handleSync(channel, makeSafeHandler(channel, handler));
|
||||
};
|
||||
|
||||
handleMessage(
|
||||
IPC_MESSAGES.GUEST_VIEW_MANAGER_CREATE_AND_ATTACH_GUEST,
|
||||
function (event, embedderFrameToken: string, elementInstanceId: number, params) {
|
||||
return createGuest(event.sender, embedderFrameToken, elementInstanceId, params);
|
||||
}
|
||||
);
|
||||
handleMessage(IPC_MESSAGES.GUEST_VIEW_MANAGER_CREATE_AND_ATTACH_GUEST, function (event, embedderFrameToken: string, elementInstanceId: number, params) {
|
||||
return createGuest(event.sender, embedderFrameToken, elementInstanceId, params);
|
||||
});
|
||||
|
||||
handleMessageSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_DETACH_GUEST, function (event, guestInstanceId: number) {
|
||||
return detachGuest(event.sender, guestInstanceId);
|
||||
@@ -320,61 +301,49 @@ ipcMainInternal.on(IPC_MESSAGES.GUEST_VIEW_MANAGER_FOCUS_CHANGE, function (event
|
||||
}
|
||||
});
|
||||
|
||||
handleMessage(
|
||||
IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL,
|
||||
function (event, guestInstanceId: number, method: string, args: any[]) {
|
||||
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
||||
if (!asyncMethods.has(method)) {
|
||||
throw new Error(`Invalid method: ${method}`);
|
||||
}
|
||||
|
||||
return (guest as any)[method](...args);
|
||||
handleMessage(IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL, function (event, guestInstanceId: number, method: string, args: any[]) {
|
||||
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
||||
if (!asyncMethods.has(method)) {
|
||||
throw new Error(`Invalid method: ${method}`);
|
||||
}
|
||||
);
|
||||
|
||||
handleMessageSync(
|
||||
IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL,
|
||||
function (event, guestInstanceId: number, method: string, args: any[]) {
|
||||
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
||||
if (!syncMethods.has(method)) {
|
||||
throw new Error(`Invalid method: ${method}`);
|
||||
}
|
||||
// Redirect history methods to updated navigationHistory property on webContents. See issue #42879.
|
||||
if (navigationHistorySyncMethods.has(method)) {
|
||||
let navigationMethod = method;
|
||||
if (method === 'clearHistory') {
|
||||
navigationMethod = 'clear';
|
||||
}
|
||||
return (guest as any).navigationHistory[navigationMethod](...args);
|
||||
}
|
||||
return (guest as any)[method](...args);
|
||||
});
|
||||
|
||||
return (guest as any)[method](...args);
|
||||
handleMessageSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL, function (event, guestInstanceId: number, method: string, args: any[]) {
|
||||
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
||||
if (!syncMethods.has(method)) {
|
||||
throw new Error(`Invalid method: ${method}`);
|
||||
}
|
||||
);
|
||||
|
||||
handleMessageSync(
|
||||
IPC_MESSAGES.GUEST_VIEW_MANAGER_PROPERTY_GET,
|
||||
function (event, guestInstanceId: number, property: string) {
|
||||
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
||||
if (!properties.has(property)) {
|
||||
throw new Error(`Invalid property: ${property}`);
|
||||
// Redirect history methods to updated navigationHistory property on webContents. See issue #42879.
|
||||
if (navigationHistorySyncMethods.has(method)) {
|
||||
let navigationMethod = method;
|
||||
if (method === 'clearHistory') {
|
||||
navigationMethod = 'clear';
|
||||
}
|
||||
|
||||
return (guest as any)[property];
|
||||
return (guest as any).navigationHistory[navigationMethod](...args);
|
||||
}
|
||||
);
|
||||
|
||||
handleMessageSync(
|
||||
IPC_MESSAGES.GUEST_VIEW_MANAGER_PROPERTY_SET,
|
||||
function (event, guestInstanceId: number, property: string, val: any) {
|
||||
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
||||
if (!properties.has(property)) {
|
||||
throw new Error(`Invalid property: ${property}`);
|
||||
}
|
||||
return (guest as any)[method](...args);
|
||||
});
|
||||
|
||||
(guest as any)[property] = val;
|
||||
handleMessageSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_PROPERTY_GET, function (event, guestInstanceId: number, property: string) {
|
||||
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
||||
if (!properties.has(property)) {
|
||||
throw new Error(`Invalid property: ${property}`);
|
||||
}
|
||||
);
|
||||
|
||||
return (guest as any)[property];
|
||||
});
|
||||
|
||||
handleMessageSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_PROPERTY_SET, function (event, guestInstanceId: number, property: string, val: any) {
|
||||
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
||||
if (!properties.has(property)) {
|
||||
throw new Error(`Invalid property: ${property}`);
|
||||
}
|
||||
|
||||
(guest as any)[property] = val;
|
||||
});
|
||||
|
||||
// Returns WebContents from its guest id hosted in given webContents.
|
||||
const getGuestForWebContents = function (guestInstanceId: number, contents: Electron.WebContents) {
|
||||
|
||||
@@ -10,37 +10,27 @@ import { parseFeatures } from '@electron/internal/browser/parse-features-string'
|
||||
import { BrowserWindow } from 'electron/main';
|
||||
import type { BrowserWindowConstructorOptions, Referrer, WebContents, LoadURLOptions } from 'electron/main';
|
||||
|
||||
type PostData = LoadURLOptions['postData'];
|
||||
type PostData = LoadURLOptions['postData']
|
||||
export type WindowOpenArgs = {
|
||||
url: string;
|
||||
frameName: string;
|
||||
features: string;
|
||||
};
|
||||
url: string,
|
||||
frameName: string,
|
||||
features: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* `openGuestWindow` is called to create and setup event handling for the new
|
||||
* window.
|
||||
*/
|
||||
export function openGuestWindow({
|
||||
embedder,
|
||||
guest,
|
||||
referrer,
|
||||
disposition,
|
||||
postData,
|
||||
overrideBrowserWindowOptions,
|
||||
windowOpenArgs,
|
||||
outlivesOpener,
|
||||
createWindow
|
||||
}: {
|
||||
embedder: WebContents;
|
||||
guest?: WebContents;
|
||||
referrer: Referrer;
|
||||
disposition: string;
|
||||
postData?: PostData;
|
||||
overrideBrowserWindowOptions?: BrowserWindowConstructorOptions;
|
||||
windowOpenArgs: WindowOpenArgs;
|
||||
outlivesOpener: boolean;
|
||||
createWindow?: Electron.CreateWindowFunction;
|
||||
export function openGuestWindow ({ embedder, guest, referrer, disposition, postData, overrideBrowserWindowOptions, windowOpenArgs, outlivesOpener, createWindow }: {
|
||||
embedder: WebContents,
|
||||
guest?: WebContents,
|
||||
referrer: Referrer,
|
||||
disposition: string,
|
||||
postData?: PostData,
|
||||
overrideBrowserWindowOptions?: BrowserWindowConstructorOptions,
|
||||
windowOpenArgs: WindowOpenArgs,
|
||||
outlivesOpener: boolean,
|
||||
createWindow?: Electron.CreateWindowFunction
|
||||
}): void {
|
||||
const { url, frameName, features } = windowOpenArgs;
|
||||
const { options: parsedOptions } = parseFeatures(features);
|
||||
@@ -60,9 +50,7 @@ export function openGuestWindow({
|
||||
|
||||
if (guest != null) {
|
||||
if (webContents !== guest) {
|
||||
throw new Error(
|
||||
'Invalid webContents. Created window should be connected to webContents passed with options object.'
|
||||
);
|
||||
throw new Error('Invalid webContents. Created window should be connected to webContents passed with options object.');
|
||||
}
|
||||
|
||||
handleWindowLifecycleEvents({ embedder, guest, outlivesOpener });
|
||||
@@ -91,14 +79,7 @@ export function openGuestWindow({
|
||||
|
||||
handleWindowLifecycleEvents({ embedder, guest: window.webContents, outlivesOpener });
|
||||
|
||||
embedder.emit('did-create-window', window, {
|
||||
url,
|
||||
frameName,
|
||||
options: browserWindowOptions,
|
||||
disposition,
|
||||
referrer,
|
||||
postData
|
||||
});
|
||||
embedder.emit('did-create-window', window, { url, frameName, options: browserWindowOptions, disposition, referrer, postData });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,14 +88,10 @@ export function openGuestWindow({
|
||||
* too is the guest destroyed; this is Electron convention and isn't based in
|
||||
* browser behavior.
|
||||
*/
|
||||
const handleWindowLifecycleEvents = function ({
|
||||
embedder,
|
||||
guest,
|
||||
outlivesOpener
|
||||
}: {
|
||||
embedder: WebContents;
|
||||
guest: WebContents;
|
||||
outlivesOpener: boolean;
|
||||
const handleWindowLifecycleEvents = function ({ embedder, guest, outlivesOpener }: {
|
||||
embedder: WebContents,
|
||||
guest: WebContents,
|
||||
outlivesOpener: boolean
|
||||
}) {
|
||||
const closedByEmbedder = function () {
|
||||
guest.removeListener('destroyed', closedByUser);
|
||||
@@ -144,25 +121,21 @@ const securityWebPreferences: { [key: string]: boolean } = {
|
||||
enableWebSQL: false
|
||||
};
|
||||
|
||||
export function makeWebPreferences({
|
||||
embedder,
|
||||
secureOverrideWebPreferences = {},
|
||||
insecureParsedWebPreferences: parsedWebPreferences = {}
|
||||
}: {
|
||||
embedder: WebContents;
|
||||
insecureParsedWebPreferences?: ReturnType<typeof parseFeatures>['webPreferences'];
|
||||
export function makeWebPreferences ({ embedder, secureOverrideWebPreferences = {}, insecureParsedWebPreferences: parsedWebPreferences = {} }: {
|
||||
embedder: WebContents,
|
||||
insecureParsedWebPreferences?: ReturnType<typeof parseFeatures>['webPreferences'],
|
||||
// Note that override preferences are considered elevated, and should only be
|
||||
// sourced from the main process, as they override security defaults. If you
|
||||
// have unvetted prefs, use parsedWebPreferences.
|
||||
secureOverrideWebPreferences?: BrowserWindowConstructorOptions['webPreferences'];
|
||||
secureOverrideWebPreferences?: BrowserWindowConstructorOptions['webPreferences'],
|
||||
}) {
|
||||
const parentWebPreferences = embedder.getLastWebPreferences()!;
|
||||
const securityWebPreferencesFromParent = Object.keys(securityWebPreferences).reduce((map, key) => {
|
||||
const securityWebPreferencesFromParent = (Object.keys(securityWebPreferences).reduce((map, key) => {
|
||||
if (securityWebPreferences[key] === parentWebPreferences[key as keyof Electron.WebPreferences]) {
|
||||
(map as any)[key] = parentWebPreferences[key as keyof Electron.WebPreferences];
|
||||
}
|
||||
return map;
|
||||
}, {} as Electron.WebPreferences);
|
||||
}, {} as Electron.WebPreferences));
|
||||
|
||||
return {
|
||||
...parsedWebPreferences,
|
||||
@@ -174,13 +147,11 @@ export function makeWebPreferences({
|
||||
};
|
||||
}
|
||||
|
||||
function formatPostDataHeaders(postData: PostData) {
|
||||
function formatPostDataHeaders (postData: PostData) {
|
||||
if (!postData) return;
|
||||
|
||||
const { contentType, boundary } = parseContentTypeFormat(postData);
|
||||
if (boundary != null) {
|
||||
return `content-type: ${contentType}; boundary=${boundary}`;
|
||||
}
|
||||
if (boundary != null) { return `content-type: ${contentType}; boundary=${boundary}`; }
|
||||
|
||||
return `content-type: ${contentType}`;
|
||||
}
|
||||
|
||||
@@ -29,11 +29,12 @@ process.on('uncaughtException', function (error) {
|
||||
// We can't import { dialog } at the top of this file as this file is
|
||||
// responsible for setting up the require hook for the "electron" module
|
||||
// so we import it inside the handler down here
|
||||
import('electron').then(({ dialog }) => {
|
||||
const stack = error.stack ? error.stack : `${error.name}: ${error.message}`;
|
||||
const message = 'Uncaught Exception:\n' + stack;
|
||||
dialog.showErrorBox('A JavaScript error occurred in the main process', message);
|
||||
});
|
||||
import('electron')
|
||||
.then(({ dialog }) => {
|
||||
const stack = error.stack ? error.stack : `${error.name}: ${error.message}`;
|
||||
const message = 'Uncaught Exception:\n' + stack;
|
||||
dialog.showErrorBox('A JavaScript error occurred in the main process', message);
|
||||
});
|
||||
});
|
||||
|
||||
// Emit 'exit' event on quit.
|
||||
@@ -63,10 +64,7 @@ if (process.platform === 'win32') {
|
||||
if (fs.existsSync(updateDotExe)) {
|
||||
const packageDir = path.dirname(path.resolve(updateDotExe));
|
||||
const packageName = path.basename(packageDir).replaceAll(/\s/g, '');
|
||||
const exeName = path
|
||||
.basename(process.execPath)
|
||||
.replace(/\.exe$/i, '')
|
||||
.replaceAll(/\s/g, '');
|
||||
const exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replaceAll(/\s/g, '');
|
||||
|
||||
app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`);
|
||||
}
|
||||
@@ -183,9 +181,7 @@ delete process.appCodeLoaded;
|
||||
if (packagePath) {
|
||||
// Finally load app's main.js and transfer control to C++.
|
||||
if ((packageJson.type === 'module' && !mainStartupScript.endsWith('.cjs')) || mainStartupScript.endsWith('.mjs')) {
|
||||
const { runEntryPointWithESMLoader } = __non_webpack_require__(
|
||||
'internal/modules/run_main'
|
||||
) as typeof import('@node/lib/internal/modules/run_main');
|
||||
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main') as typeof import('@node/lib/internal/modules/run_main');
|
||||
const main = (require('url') as typeof url).pathToFileURL(path.join(packagePath, mainStartupScript));
|
||||
runEntryPointWithESMLoader(async (cascadedLoader: any) => {
|
||||
try {
|
||||
@@ -203,6 +199,6 @@ if (packagePath) {
|
||||
}
|
||||
} else {
|
||||
console.error('Failed to locate a valid package to load (app, app.asar or default_app.asar)');
|
||||
console.error("This normally means you've damaged the Electron package somehow");
|
||||
console.error('This normally means you\'ve damaged the Electron package somehow');
|
||||
appCodeLoaded!();
|
||||
}
|
||||
|
||||
@@ -21,14 +21,10 @@ const addReturnValueToEvent = (event: Electron.IpcMainEvent | Electron.IpcMainSe
|
||||
});
|
||||
};
|
||||
|
||||
const getServiceWorkerFromEvent = (
|
||||
event: Electron.IpcMainServiceWorkerEvent | Electron.IpcMainServiceWorkerInvokeEvent
|
||||
): ServiceWorkerMain | undefined => {
|
||||
const getServiceWorkerFromEvent = (event: Electron.IpcMainServiceWorkerEvent | Electron.IpcMainServiceWorkerInvokeEvent): ServiceWorkerMain | undefined => {
|
||||
return event.session.serviceWorkers._getWorkerFromVersionIDIfExists(event.versionId);
|
||||
};
|
||||
const addServiceWorkerPropertyToEvent = (
|
||||
event: Electron.IpcMainServiceWorkerEvent | Electron.IpcMainServiceWorkerInvokeEvent
|
||||
) => {
|
||||
const addServiceWorkerPropertyToEvent = (event: Electron.IpcMainServiceWorkerEvent | Electron.IpcMainServiceWorkerInvokeEvent) => {
|
||||
Object.defineProperty(event, 'serviceWorker', {
|
||||
get: () => event.session.serviceWorkers.getWorkerFromVersionID(event.versionId)
|
||||
});
|
||||
@@ -45,9 +41,7 @@ const cachedIpcEmitters: (ElectronInternal.IpcMainInternal | undefined)[] = [
|
||||
];
|
||||
|
||||
// Get list of relevant IPC emitters for dispatch.
|
||||
const getIpcEmittersForFrameEvent = (
|
||||
event: Electron.IpcMainEvent | Electron.IpcMainInvokeEvent
|
||||
): (ElectronInternal.IpcMainInternal | undefined)[] => {
|
||||
const getIpcEmittersForFrameEvent = (event: Electron.IpcMainEvent | Electron.IpcMainInvokeEvent): (ElectronInternal.IpcMainInternal | undefined)[] => {
|
||||
// Lookup by FrameTreeNode ID to ensure IPCs received after a frame swap are
|
||||
// always received. This occurs when a RenderFrame sends an IPC while it's
|
||||
// unloading and its internal state is pending deletion.
|
||||
@@ -61,121 +55,97 @@ const getIpcEmittersForFrameEvent = (
|
||||
/**
|
||||
* Listens for IPC dispatch events on `api`.
|
||||
*/
|
||||
export function addIpcDispatchListeners(api: NodeJS.EventEmitter) {
|
||||
api.on(
|
||||
'-ipc-message' as any,
|
||||
function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, args: any[]) {
|
||||
const internal = v8Util.getHiddenValue<boolean>(event, 'internal');
|
||||
export function addIpcDispatchListeners (api: NodeJS.EventEmitter) {
|
||||
api.on('-ipc-message' as any, function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, args: any[]) {
|
||||
const internal = v8Util.getHiddenValue<boolean>(event, 'internal');
|
||||
|
||||
if (internal) {
|
||||
ipcMainInternal.emit(channel, event, ...args);
|
||||
} else if (event.type === 'frame') {
|
||||
addReplyToEvent(event);
|
||||
event.sender.emit('ipc-message', event, channel, ...args);
|
||||
for (const ipcEmitter of getIpcEmittersForFrameEvent(event)) {
|
||||
ipcEmitter?.emit(channel, event, ...args);
|
||||
}
|
||||
} else if (event.type === 'service-worker') {
|
||||
addServiceWorkerPropertyToEvent(event);
|
||||
getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, ...args);
|
||||
if (internal) {
|
||||
ipcMainInternal.emit(channel, event, ...args);
|
||||
} else if (event.type === 'frame') {
|
||||
addReplyToEvent(event);
|
||||
event.sender.emit('ipc-message', event, channel, ...args);
|
||||
for (const ipcEmitter of getIpcEmittersForFrameEvent(event)) {
|
||||
ipcEmitter?.emit(channel, event, ...args);
|
||||
}
|
||||
} as any
|
||||
);
|
||||
} else if (event.type === 'service-worker') {
|
||||
addServiceWorkerPropertyToEvent(event);
|
||||
getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, ...args);
|
||||
}
|
||||
} as any);
|
||||
|
||||
api.on(
|
||||
'-ipc-invoke' as any,
|
||||
async function (
|
||||
event: Electron.IpcMainInvokeEvent | Electron.IpcMainServiceWorkerInvokeEvent,
|
||||
channel: string,
|
||||
args: any[]
|
||||
) {
|
||||
const internal = v8Util.getHiddenValue<boolean>(event, 'internal');
|
||||
api.on('-ipc-invoke' as any, async function (event: Electron.IpcMainInvokeEvent | Electron.IpcMainServiceWorkerInvokeEvent, channel: string, args: any[]) {
|
||||
const internal = v8Util.getHiddenValue<boolean>(event, 'internal');
|
||||
|
||||
const replyWithResult = (result: any) => event._replyChannel.sendReply({ result });
|
||||
const replyWithError = (error: Error) => {
|
||||
console.error(`Error occurred in handler for '${channel}':`, error);
|
||||
event._replyChannel.sendReply({ error: error.toString() });
|
||||
};
|
||||
const replyWithResult = (result: any) => event._replyChannel.sendReply({ result });
|
||||
const replyWithError = (error: Error) => {
|
||||
console.error(`Error occurred in handler for '${channel}':`, error);
|
||||
event._replyChannel.sendReply({ error: error.toString() });
|
||||
};
|
||||
|
||||
const targets: (Electron.IpcMainServiceWorker | ElectronInternal.IpcMainInternal | undefined)[] = [];
|
||||
const targets: (Electron.IpcMainServiceWorker | ElectronInternal.IpcMainInternal | undefined)[] = [];
|
||||
|
||||
if (internal) {
|
||||
targets.push(ipcMainInternal);
|
||||
} else if (event.type === 'frame') {
|
||||
targets.push(...getIpcEmittersForFrameEvent(event));
|
||||
} else if (event.type === 'service-worker') {
|
||||
addServiceWorkerPropertyToEvent(event);
|
||||
const workerIpc = getServiceWorkerFromEvent(event)?.ipc;
|
||||
targets.push(workerIpc);
|
||||
if (internal) {
|
||||
targets.push(ipcMainInternal);
|
||||
} else if (event.type === 'frame') {
|
||||
targets.push(...getIpcEmittersForFrameEvent(event));
|
||||
} else if (event.type === 'service-worker') {
|
||||
addServiceWorkerPropertyToEvent(event);
|
||||
const workerIpc = getServiceWorkerFromEvent(event)?.ipc;
|
||||
targets.push(workerIpc);
|
||||
}
|
||||
|
||||
const target = targets.find(target => (target as any)?._invokeHandlers.has(channel));
|
||||
if (target) {
|
||||
const handler = (target as any)._invokeHandlers.get(channel);
|
||||
try {
|
||||
replyWithResult(await Promise.resolve(handler(event, ...args)));
|
||||
} catch (err) {
|
||||
replyWithError(err as Error);
|
||||
}
|
||||
} else {
|
||||
replyWithError(new Error(`No handler registered for '${channel}'`));
|
||||
}
|
||||
} as any);
|
||||
|
||||
const target = targets.find((target) => (target as any)?._invokeHandlers.has(channel));
|
||||
if (target) {
|
||||
const handler = (target as any)._invokeHandlers.get(channel);
|
||||
try {
|
||||
replyWithResult(await Promise.resolve(handler(event, ...args)));
|
||||
} catch (err) {
|
||||
replyWithError(err as Error);
|
||||
}
|
||||
} else {
|
||||
replyWithError(new Error(`No handler registered for '${channel}'`));
|
||||
api.on('-ipc-message-sync' as any, function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, args: any[]) {
|
||||
const internal = v8Util.getHiddenValue<boolean>(event, 'internal');
|
||||
addReturnValueToEvent(event);
|
||||
if (internal) {
|
||||
ipcMainInternal.emit(channel, event, ...args);
|
||||
} else if (event.type === 'frame') {
|
||||
addReplyToEvent(event);
|
||||
const webContents = event.sender;
|
||||
const ipcEmitters = getIpcEmittersForFrameEvent(event);
|
||||
if (
|
||||
webContents.listenerCount('ipc-message-sync') === 0 &&
|
||||
ipcEmitters.every(emitter => !emitter || emitter.listenerCount(channel) === 0)
|
||||
) {
|
||||
console.warn(`WebContents #${webContents.id} called ipcRenderer.sendSync() with '${channel}' channel without listeners.`);
|
||||
}
|
||||
} as any
|
||||
);
|
||||
|
||||
api.on(
|
||||
'-ipc-message-sync' as any,
|
||||
function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, args: any[]) {
|
||||
const internal = v8Util.getHiddenValue<boolean>(event, 'internal');
|
||||
addReturnValueToEvent(event);
|
||||
if (internal) {
|
||||
ipcMainInternal.emit(channel, event, ...args);
|
||||
} else if (event.type === 'frame') {
|
||||
addReplyToEvent(event);
|
||||
const webContents = event.sender;
|
||||
const ipcEmitters = getIpcEmittersForFrameEvent(event);
|
||||
if (
|
||||
webContents.listenerCount('ipc-message-sync') === 0 &&
|
||||
ipcEmitters.every((emitter) => !emitter || emitter.listenerCount(channel) === 0)
|
||||
) {
|
||||
console.warn(
|
||||
`WebContents #${webContents.id} called ipcRenderer.sendSync() with '${channel}' channel without listeners.`
|
||||
);
|
||||
}
|
||||
webContents.emit('ipc-message-sync', event, channel, ...args);
|
||||
for (const ipcEmitter of ipcEmitters) {
|
||||
ipcEmitter?.emit(channel, event, ...args);
|
||||
}
|
||||
} else if (event.type === 'service-worker') {
|
||||
addServiceWorkerPropertyToEvent(event);
|
||||
getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, ...args);
|
||||
webContents.emit('ipc-message-sync', event, channel, ...args);
|
||||
for (const ipcEmitter of ipcEmitters) {
|
||||
ipcEmitter?.emit(channel, event, ...args);
|
||||
}
|
||||
} as any
|
||||
);
|
||||
} else if (event.type === 'service-worker') {
|
||||
addServiceWorkerPropertyToEvent(event);
|
||||
getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, ...args);
|
||||
}
|
||||
} as any);
|
||||
|
||||
api.on('-ipc-message-host', function (event: Electron.IpcMainEvent, channel: string, args: any[]) {
|
||||
event.sender.emit('-ipc-message-host', event, channel, args);
|
||||
});
|
||||
|
||||
api.on(
|
||||
'-ipc-ports' as any,
|
||||
function (
|
||||
event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent,
|
||||
channel: string,
|
||||
message: any,
|
||||
ports: any[]
|
||||
) {
|
||||
event.ports = ports.map((p) => new MessagePortMain(p));
|
||||
if (event.type === 'frame') {
|
||||
const ipcEmitters = getIpcEmittersForFrameEvent(event);
|
||||
for (const ipcEmitter of ipcEmitters) {
|
||||
ipcEmitter?.emit(channel, event, message);
|
||||
}
|
||||
api.on('-ipc-ports' as any, function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, message: any, ports: any[]) {
|
||||
event.ports = ports.map(p => new MessagePortMain(p));
|
||||
if (event.type === 'frame') {
|
||||
const ipcEmitters = getIpcEmittersForFrameEvent(event);
|
||||
for (const ipcEmitter of ipcEmitters) {
|
||||
ipcEmitter?.emit(channel, event, message);
|
||||
}
|
||||
if (event.type === 'service-worker') {
|
||||
addServiceWorkerPropertyToEvent(event);
|
||||
getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, message);
|
||||
}
|
||||
} as any
|
||||
);
|
||||
} if (event.type === 'service-worker') {
|
||||
addServiceWorkerPropertyToEvent(event);
|
||||
getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, message);
|
||||
}
|
||||
} as any);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { EventEmitter } from 'events';
|
||||
export class IpcMainImpl extends EventEmitter implements Electron.IpcMain {
|
||||
private _invokeHandlers: Map<string, (e: IpcMainInvokeEvent, ...args: any[]) => void> = new Map();
|
||||
|
||||
constructor() {
|
||||
constructor () {
|
||||
super();
|
||||
|
||||
// Do not throw exception when channel name is "error".
|
||||
@@ -29,7 +29,7 @@ export class IpcMainImpl extends EventEmitter implements Electron.IpcMain {
|
||||
});
|
||||
};
|
||||
|
||||
removeHandler(method: string) {
|
||||
removeHandler (method: string) {
|
||||
this._invokeHandlers.delete(method);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
|
||||
|
||||
type IPCHandler = (event: ElectronInternal.IpcMainInternalEvent, ...args: any[]) => any;
|
||||
type IPCHandler = (event: ElectronInternal.IpcMainInternalEvent, ...args: any[]) => any
|
||||
|
||||
export const handleSync = function <T extends IPCHandler>(channel: string, handler: T) {
|
||||
export const handleSync = function <T extends IPCHandler> (channel: string, handler: T) {
|
||||
ipcMainInternal.on(channel, async (event, ...args) => {
|
||||
try {
|
||||
event.returnValue = [null, await handler(event, ...args)];
|
||||
@@ -14,11 +14,11 @@ export const handleSync = function <T extends IPCHandler>(channel: string, handl
|
||||
|
||||
let nextId = 0;
|
||||
|
||||
export function invokeInWebContents<T>(sender: Electron.WebContents, command: string, ...args: any[]) {
|
||||
export function invokeInWebContents<T> (sender: Electron.WebContents, command: string, ...args: any[]) {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const requestId = ++nextId;
|
||||
const channel = `${command}_RESPONSE_${requestId}`;
|
||||
ipcMainInternal.on(channel, function handler(event, error: Error, result: any) {
|
||||
ipcMainInternal.on(channel, function handler (event, error: Error, result: any) {
|
||||
if (event.type !== 'frame' || event.sender !== sender) {
|
||||
console.error(`Reply to ${command} sent by unexpected sender`);
|
||||
return;
|
||||
@@ -37,12 +37,12 @@ export function invokeInWebContents<T>(sender: Electron.WebContents, command: st
|
||||
});
|
||||
}
|
||||
|
||||
export function invokeInWebFrameMain<T>(sender: Electron.WebFrameMain, command: string, ...args: any[]) {
|
||||
export function invokeInWebFrameMain<T> (sender: Electron.WebFrameMain, command: string, ...args: any[]) {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const requestId = ++nextId;
|
||||
const channel = `${command}_RESPONSE_${requestId}`;
|
||||
const frameTreeNodeId = sender.frameTreeNodeId;
|
||||
ipcMainInternal.on(channel, function handler(event, error: Error, result: any) {
|
||||
ipcMainInternal.on(channel, function handler (event, error: Error, result: any) {
|
||||
if (event.type !== 'frame' || event.frameTreeNodeId !== frameTreeNodeId) {
|
||||
console.error(`Reply to ${command} sent by unexpected sender`);
|
||||
return;
|
||||
|
||||
@@ -2,28 +2,26 @@ import { EventEmitter } from 'events';
|
||||
|
||||
export class MessagePortMain extends EventEmitter implements Electron.MessagePortMain {
|
||||
_internalPort: any;
|
||||
constructor(internalPort: any) {
|
||||
constructor (internalPort: any) {
|
||||
super();
|
||||
this._internalPort = internalPort;
|
||||
this._internalPort.emit = (channel: string, event: { ports: any[] }) => {
|
||||
if (channel === 'message') {
|
||||
event = { ...event, ports: event.ports.map((p) => new MessagePortMain(p)) };
|
||||
}
|
||||
this._internalPort.emit = (channel: string, event: {ports: any[]}) => {
|
||||
if (channel === 'message') { event = { ...event, ports: event.ports.map(p => new MessagePortMain(p)) }; }
|
||||
this.emit(channel, event);
|
||||
};
|
||||
}
|
||||
|
||||
start() {
|
||||
start () {
|
||||
return this._internalPort.start();
|
||||
}
|
||||
|
||||
close() {
|
||||
close () {
|
||||
return this._internalPort.close();
|
||||
}
|
||||
|
||||
postMessage(...args: any[]) {
|
||||
postMessage (...args: any[]) {
|
||||
if (Array.isArray(args[1])) {
|
||||
args[1] = args[1].map((o: any) => (o instanceof MessagePortMain ? o._internalPort : o));
|
||||
args[1] = args[1].map((o: any) => o instanceof MessagePortMain ? o._internalPort : o);
|
||||
}
|
||||
return this._internalPort.postMessage(...args);
|
||||
}
|
||||
|
||||
@@ -6,15 +6,14 @@ import { BrowserWindowConstructorOptions } from 'electron/main';
|
||||
|
||||
type RequiredBrowserWindowConstructorOptions = Required<BrowserWindowConstructorOptions>;
|
||||
type IntegerBrowserWindowOptionKeys = {
|
||||
[K in keyof RequiredBrowserWindowConstructorOptions]: RequiredBrowserWindowConstructorOptions[K] extends number
|
||||
? K
|
||||
: never;
|
||||
[K in keyof RequiredBrowserWindowConstructorOptions]:
|
||||
RequiredBrowserWindowConstructorOptions[K] extends number ? K : never
|
||||
}[keyof RequiredBrowserWindowConstructorOptions];
|
||||
|
||||
// This could be an array of keys, but an object allows us to add a compile-time
|
||||
// check validating that we haven't added an integer property to
|
||||
// BrowserWindowConstructorOptions that this module doesn't know about.
|
||||
const keysOfTypeNumberCompileTimeCheck: { [K in IntegerBrowserWindowOptionKeys]: true } = {
|
||||
const keysOfTypeNumberCompileTimeCheck: { [K in IntegerBrowserWindowOptionKeys] : true } = {
|
||||
x: true,
|
||||
y: true,
|
||||
width: true,
|
||||
@@ -32,13 +31,7 @@ const keysOfTypeNumberCompileTimeCheck: { [K in IntegerBrowserWindowOptionKeys]:
|
||||
// to `innerWidth` / `innerHeight`. However, our implementation currently incorrectly maps
|
||||
// `width` and `height` to `outerWidth` and `outerHeight`, or the size of the window
|
||||
// with all border and related window chrome.
|
||||
const keysOfTypeNumber = new Set([
|
||||
'top',
|
||||
'left',
|
||||
'innerWidth',
|
||||
'innerHeight',
|
||||
...Object.keys(keysOfTypeNumberCompileTimeCheck)
|
||||
]);
|
||||
const keysOfTypeNumber = new Set(['top', 'left', 'innerWidth', 'innerHeight', ...Object.keys(keysOfTypeNumberCompileTimeCheck)]);
|
||||
|
||||
/**
|
||||
* Note that we only allow "0" and "1" boolean conversion when the type is known
|
||||
@@ -48,7 +41,7 @@ const keysOfTypeNumber = new Set([
|
||||
* https://html.spec.whatwg.org/multipage/window-object.html#concept-window-open-features-parse-boolean
|
||||
*/
|
||||
type CoercedValue = string | number | boolean;
|
||||
function coerce(key: string, value: string): CoercedValue {
|
||||
function coerce (key: string, value: string): CoercedValue {
|
||||
if (keysOfTypeNumber.has(key)) {
|
||||
return parseInt(value, 10);
|
||||
}
|
||||
@@ -68,35 +61,27 @@ function coerce(key: string, value: string): CoercedValue {
|
||||
}
|
||||
}
|
||||
|
||||
export function parseCommaSeparatedKeyValue(source: string) {
|
||||
export function parseCommaSeparatedKeyValue (source: string) {
|
||||
const parsed = {} as { [key: string]: any };
|
||||
for (const keyValuePair of source.split(',')) {
|
||||
const [key, value] = keyValuePair.split('=').map((str) => str.trim());
|
||||
if (key) {
|
||||
parsed[key] = coerce(key, value);
|
||||
}
|
||||
const [key, value] = keyValuePair.split('=').map(str => str.trim());
|
||||
if (key) { parsed[key] = coerce(key, value); }
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export function parseWebViewWebPreferences(preferences: string) {
|
||||
export function parseWebViewWebPreferences (preferences: string) {
|
||||
return parseCommaSeparatedKeyValue(preferences);
|
||||
}
|
||||
|
||||
const allowedWebPreferences = [
|
||||
'zoomFactor',
|
||||
'nodeIntegration',
|
||||
'javascript',
|
||||
'contextIsolation',
|
||||
'webviewTag'
|
||||
] as const;
|
||||
const allowedWebPreferences = ['zoomFactor', 'nodeIntegration', 'javascript', 'contextIsolation', 'webviewTag'] as const;
|
||||
type AllowedWebPreference = (typeof allowedWebPreferences)[number];
|
||||
|
||||
/**
|
||||
* Parses a feature string that has the format used in window.open().
|
||||
*/
|
||||
export function parseFeatures(features: string) {
|
||||
export function parseFeatures (features: string) {
|
||||
const parsed = parseCommaSeparatedKeyValue(features);
|
||||
|
||||
const webPreferences: { [K in AllowedWebPreference]?: any } = {};
|
||||
|
||||
@@ -54,19 +54,19 @@ const getPreloadScriptsFromEvent = (event: ElectronInternal.IpcMainInternalEvent
|
||||
let preloadScripts = session.getPreloadScripts();
|
||||
|
||||
if (event.type === 'frame') {
|
||||
preloadScripts = preloadScripts.filter((script) => script.type === 'frame');
|
||||
preloadScripts = preloadScripts.filter(script => script.type === 'frame');
|
||||
|
||||
const webPrefPreload = event.sender._getPreloadScript();
|
||||
if (webPrefPreload) preloadScripts.push(webPrefPreload);
|
||||
} else if (event.type === 'service-worker') {
|
||||
preloadScripts = preloadScripts.filter((script) => script.type === 'service-worker');
|
||||
preloadScripts = preloadScripts.filter(script => script.type === 'service-worker');
|
||||
} else {
|
||||
throw new Error(`getPreloadScriptsFromEvent: event.type is invalid (${(event as any).type})`);
|
||||
}
|
||||
|
||||
// TODO(samuelmaddock): Remove filter after Session.setPreloads is fully
|
||||
// deprecated. The new API will prevent relative paths from being registered.
|
||||
return preloadScripts.filter((script) => path.isAbsolute(script.filePath));
|
||||
return preloadScripts.filter(script => path.isAbsolute(script.filePath));
|
||||
};
|
||||
|
||||
const readPreloadScript = async function (script: Electron.PreloadScript): Promise<ElectronInternal.PreloadScript> {
|
||||
@@ -103,7 +103,7 @@ ipcMainUtils.handleSync(IPC_MESSAGES.BROWSER_SANDBOX_LOAD, async function (event
|
||||
|
||||
ipcMainUtils.handleSync(IPC_MESSAGES.BROWSER_NONSANDBOX_LOAD, function (event) {
|
||||
const preloadScripts = getPreloadScriptsFromEvent(event);
|
||||
return { preloadPaths: preloadScripts.map((script) => script.filePath) };
|
||||
return { preloadPaths: preloadScripts.map(script => script.filePath) };
|
||||
});
|
||||
|
||||
ipcMainInternal.on(IPC_MESSAGES.BROWSER_PRELOAD_ERROR, function (event, preloadPath: string, error: Error) {
|
||||
|
||||
@@ -17,14 +17,7 @@ export const webViewEvents: Record<string, readonly string[]> = {
|
||||
'did-start-navigation': ['url', 'isInPlace', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
|
||||
'did-redirect-navigation': ['url', 'isInPlace', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
|
||||
'did-navigate': ['url', 'httpResponseCode', 'httpStatusText'],
|
||||
'did-frame-navigate': [
|
||||
'url',
|
||||
'httpResponseCode',
|
||||
'httpStatusText',
|
||||
'isMainFrame',
|
||||
'frameProcessId',
|
||||
'frameRoutingId'
|
||||
],
|
||||
'did-frame-navigate': ['url', 'httpResponseCode', 'httpStatusText', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
|
||||
'did-navigate-in-page': ['url', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
|
||||
'-focus-change': ['focus'],
|
||||
close: [],
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import type { ClientRequestConstructorOptions, UploadProgress } from 'electron/common';
|
||||
import type {
|
||||
ClientRequestConstructorOptions,
|
||||
UploadProgress
|
||||
} from 'electron/common';
|
||||
|
||||
import { Readable, Writable } from 'stream';
|
||||
import * as url from 'url';
|
||||
|
||||
const { isValidHeaderName, isValidHeaderValue, createURLLoader } = process._linkedBinding('electron_common_net');
|
||||
const {
|
||||
isValidHeaderName,
|
||||
isValidHeaderValue,
|
||||
createURLLoader
|
||||
} = process._linkedBinding('electron_common_net');
|
||||
|
||||
const kHttpProtocols = new Set(['http:', 'https:']);
|
||||
|
||||
@@ -36,20 +43,20 @@ class IncomingMessage extends Readable {
|
||||
_responseHead: NodeJS.ResponseHead;
|
||||
_resume: (() => void) | null = null;
|
||||
|
||||
constructor(responseHead: NodeJS.ResponseHead) {
|
||||
constructor (responseHead: NodeJS.ResponseHead) {
|
||||
super();
|
||||
this._responseHead = responseHead;
|
||||
}
|
||||
|
||||
get statusCode() {
|
||||
get statusCode () {
|
||||
return this._responseHead.statusCode;
|
||||
}
|
||||
|
||||
get statusMessage() {
|
||||
get statusMessage () {
|
||||
return this._responseHead.statusMessage;
|
||||
}
|
||||
|
||||
get headers() {
|
||||
get headers () {
|
||||
const filteredHeaders: Record<string, string | string[]> = {};
|
||||
const { headers, rawHeaders } = this._responseHead;
|
||||
for (const [name, values] of Object.entries(headers)) {
|
||||
@@ -58,13 +65,11 @@ class IncomingMessage extends Readable {
|
||||
const cookies = rawHeaders.filter(({ key }) => key.toLowerCase() === 'set-cookie').map(({ value }) => value);
|
||||
// keep set-cookie as an array per Node.js rules
|
||||
// see https://nodejs.org/api/http.html#http_message_headers
|
||||
if (cookies.length) {
|
||||
filteredHeaders['set-cookie'] = cookies;
|
||||
}
|
||||
if (cookies.length) { filteredHeaders['set-cookie'] = cookies; }
|
||||
return filteredHeaders;
|
||||
}
|
||||
|
||||
get rawHeaders() {
|
||||
get rawHeaders () {
|
||||
const rawHeadersArr: string[] = [];
|
||||
const { rawHeaders } = this._responseHead;
|
||||
for (const header of rawHeaders) {
|
||||
@@ -73,34 +78,34 @@ class IncomingMessage extends Readable {
|
||||
return rawHeadersArr;
|
||||
}
|
||||
|
||||
get httpVersion() {
|
||||
get httpVersion () {
|
||||
return `${this.httpVersionMajor}.${this.httpVersionMinor}`;
|
||||
}
|
||||
|
||||
get httpVersionMajor() {
|
||||
get httpVersionMajor () {
|
||||
return this._responseHead.httpVersion.major;
|
||||
}
|
||||
|
||||
get httpVersionMinor() {
|
||||
get httpVersionMinor () {
|
||||
return this._responseHead.httpVersion.minor;
|
||||
}
|
||||
|
||||
get rawTrailers() {
|
||||
get rawTrailers () {
|
||||
throw new Error('HTTP trailers are not supported');
|
||||
}
|
||||
|
||||
get trailers() {
|
||||
get trailers () {
|
||||
throw new Error('HTTP trailers are not supported');
|
||||
}
|
||||
|
||||
_storeInternalData(chunk: Buffer | null, resume: (() => void) | null) {
|
||||
_storeInternalData (chunk: Buffer | null, resume: (() => void) | null) {
|
||||
// save the network callback for use in _pushInternalData
|
||||
this._resume = resume;
|
||||
this._data.push(chunk);
|
||||
this._pushInternalData();
|
||||
}
|
||||
|
||||
_pushInternalData() {
|
||||
_pushInternalData () {
|
||||
while (this._shouldPush && this._data.length > 0) {
|
||||
const chunk = this._data.shift();
|
||||
this._shouldPush = this.push(chunk);
|
||||
@@ -116,7 +121,7 @@ class IncomingMessage extends Readable {
|
||||
}
|
||||
}
|
||||
|
||||
_read() {
|
||||
_read () {
|
||||
this._shouldPush = true;
|
||||
this._pushInternalData();
|
||||
}
|
||||
@@ -125,19 +130,17 @@ class IncomingMessage extends Readable {
|
||||
/** Writable stream that buffers up everything written to it. */
|
||||
class SlurpStream extends Writable {
|
||||
_data: Buffer;
|
||||
constructor() {
|
||||
constructor () {
|
||||
super();
|
||||
this._data = Buffer.alloc(0);
|
||||
}
|
||||
|
||||
_write(chunk: Buffer, encoding: string, callback: () => void) {
|
||||
_write (chunk: Buffer, encoding: string, callback: () => void) {
|
||||
this._data = Buffer.concat([this._data, chunk]);
|
||||
callback();
|
||||
}
|
||||
|
||||
data() {
|
||||
return this._data;
|
||||
}
|
||||
data () { return this._data; }
|
||||
}
|
||||
|
||||
class ChunkedBodyStream extends Writable {
|
||||
@@ -146,12 +149,12 @@ class ChunkedBodyStream extends Writable {
|
||||
_pendingCallback?: (error?: Error) => void;
|
||||
_clientRequest: ClientRequest;
|
||||
|
||||
constructor(clientRequest: ClientRequest) {
|
||||
constructor (clientRequest: ClientRequest) {
|
||||
super();
|
||||
this._clientRequest = clientRequest;
|
||||
}
|
||||
|
||||
_write(chunk: Buffer, encoding: string, callback: () => void) {
|
||||
_write (chunk: Buffer, encoding: string, callback: () => void) {
|
||||
if (this._downstream) {
|
||||
this._downstream.write(chunk).then(callback, callback);
|
||||
} else {
|
||||
@@ -165,12 +168,12 @@ class ChunkedBodyStream extends Writable {
|
||||
}
|
||||
}
|
||||
|
||||
_final(callback: () => void) {
|
||||
_final (callback: () => void) {
|
||||
this._downstream!.done();
|
||||
callback();
|
||||
}
|
||||
|
||||
startReading(pipe: NodeJS.DataPipe) {
|
||||
startReading (pipe: NodeJS.DataPipe) {
|
||||
if (this._downstream) {
|
||||
throw new Error('two startReading calls???');
|
||||
}
|
||||
@@ -195,7 +198,7 @@ class ChunkedBodyStream extends Writable {
|
||||
type RedirectPolicy = 'manual' | 'follow' | 'error';
|
||||
|
||||
const kAllowNonHttpProtocols = Symbol('kAllowNonHttpProtocols');
|
||||
export function allowAnyProtocol(opts: ClientRequestConstructorOptions): ClientRequestConstructorOptions {
|
||||
export function allowAnyProtocol (opts: ClientRequestConstructorOptions): ClientRequestConstructorOptions {
|
||||
return {
|
||||
...opts,
|
||||
[kAllowNonHttpProtocols]: true
|
||||
@@ -203,12 +206,12 @@ export function allowAnyProtocol(opts: ClientRequestConstructorOptions): ClientR
|
||||
}
|
||||
|
||||
type ExtraURLLoaderOptions = {
|
||||
redirectPolicy: RedirectPolicy;
|
||||
headers: Record<string, { name: string; value: string | string[] }>;
|
||||
allowNonHttpProtocols: boolean;
|
||||
};
|
||||
redirectPolicy: RedirectPolicy;
|
||||
headers: Record<string, { name: string, value: string | string[] }>;
|
||||
allowNonHttpProtocols: boolean;
|
||||
}
|
||||
|
||||
function validateHeader(name: any, value: any): void {
|
||||
function validateHeader (name: any, value: any): void {
|
||||
if (typeof name !== 'string') {
|
||||
throw new TypeError('`name` should be a string in setHeader(name, value)');
|
||||
}
|
||||
@@ -223,9 +226,7 @@ function validateHeader(name: any, value: any): void {
|
||||
}
|
||||
}
|
||||
|
||||
function parseOptions(
|
||||
optionsIn: ClientRequestConstructorOptions | string
|
||||
): NodeJS.CreateURLLoaderOptions & ExtraURLLoaderOptions {
|
||||
function parseOptions (optionsIn: ClientRequestConstructorOptions | string): NodeJS.CreateURLLoaderOptions & ExtraURLLoaderOptions {
|
||||
const options: any = typeof optionsIn === 'string' ? new URL(optionsIn) : { ...optionsIn };
|
||||
|
||||
let urlStr: string = options.url || options.href;
|
||||
@@ -275,11 +276,7 @@ function parseOptions(
|
||||
throw new TypeError('headers must be an object');
|
||||
}
|
||||
|
||||
const urlLoaderOptions: NodeJS.CreateURLLoaderOptions & {
|
||||
redirectPolicy: RedirectPolicy;
|
||||
headers: Record<string, { name: string; value: string | string[] }>;
|
||||
allowNonHttpProtocols: boolean;
|
||||
} = {
|
||||
const urlLoaderOptions: NodeJS.CreateURLLoaderOptions & { redirectPolicy: RedirectPolicy, headers: Record<string, { name: string, value: string | string[] }>, allowNonHttpProtocols: boolean } = {
|
||||
method: (options.method || 'GET').toUpperCase(),
|
||||
url: urlStr,
|
||||
redirectPolicy,
|
||||
@@ -306,9 +303,7 @@ function parseOptions(
|
||||
if (process.type !== 'utility') {
|
||||
const { Session } = process._linkedBinding('electron_browser_session');
|
||||
if (options.session) {
|
||||
if (!(options.session instanceof Session)) {
|
||||
throw new TypeError('`session` should be an instance of the Session class');
|
||||
}
|
||||
if (!(options.session instanceof Session)) { throw new TypeError('`session` should be an instance of the Session class'); }
|
||||
urlLoaderOptions.session = options.session;
|
||||
} else if (options.partition) {
|
||||
if (typeof options.partition === 'string') {
|
||||
@@ -327,16 +322,14 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
|
||||
_aborted: boolean = false;
|
||||
_chunkedEncoding: boolean | undefined;
|
||||
_body: Writable | undefined;
|
||||
_urlLoaderOptions: NodeJS.CreateURLLoaderOptions & {
|
||||
headers: Record<string, { name: string; value: string | string[] }>;
|
||||
};
|
||||
_urlLoaderOptions: NodeJS.CreateURLLoaderOptions & { headers: Record<string, { name: string, value: string | string[] }> };
|
||||
_redirectPolicy: RedirectPolicy;
|
||||
_followRedirectCb?: () => void;
|
||||
_uploadProgress?: { active: boolean; started: boolean; current: number; total: number };
|
||||
_uploadProgress?: { active: boolean, started: boolean, current: number, total: number };
|
||||
_urlLoader?: NodeJS.URLLoader;
|
||||
_response?: IncomingMessage;
|
||||
|
||||
constructor(options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
|
||||
constructor (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
|
||||
super({ autoDestroy: true });
|
||||
|
||||
if (callback) {
|
||||
@@ -348,18 +341,16 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
|
||||
if (!urlLoaderOptions.allowNonHttpProtocols && !kHttpProtocols.has(urlObj.protocol)) {
|
||||
throw new Error('ClientRequest only supports http: and https: protocols');
|
||||
}
|
||||
if (urlLoaderOptions.credentials === 'same-origin' && !urlLoaderOptions.origin) {
|
||||
throw new Error('credentials: same-origin requires origin to be set');
|
||||
}
|
||||
if (urlLoaderOptions.credentials === 'same-origin' && !urlLoaderOptions.origin) { throw new Error('credentials: same-origin requires origin to be set'); }
|
||||
this._urlLoaderOptions = urlLoaderOptions;
|
||||
this._redirectPolicy = redirectPolicy;
|
||||
}
|
||||
|
||||
get chunkedEncoding() {
|
||||
get chunkedEncoding () {
|
||||
return this._chunkedEncoding || false;
|
||||
}
|
||||
|
||||
set chunkedEncoding(value: boolean) {
|
||||
set chunkedEncoding (value: boolean) {
|
||||
if (this._started) {
|
||||
throw new Error('chunkedEncoding can only be set before the request is started');
|
||||
}
|
||||
@@ -375,9 +366,9 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
|
||||
}
|
||||
}
|
||||
|
||||
setHeader(name: string, value: string) {
|
||||
setHeader (name: string, value: string) {
|
||||
if (this._started || this._firstWrite) {
|
||||
throw new Error("Can't set headers after they are sent");
|
||||
throw new Error('Can\'t set headers after they are sent');
|
||||
}
|
||||
validateHeader(name, value);
|
||||
|
||||
@@ -385,30 +376,30 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
|
||||
this._urlLoaderOptions.headers[key] = { name, value };
|
||||
}
|
||||
|
||||
getHeader(name: string) {
|
||||
getHeader (name: string) {
|
||||
if (name == null) {
|
||||
throw new Error('`name` is required for getHeader(name)');
|
||||
}
|
||||
|
||||
const key = name.toLowerCase();
|
||||
const header = this._urlLoaderOptions.headers[key];
|
||||
return header && (header.value as any);
|
||||
return header && header.value as any;
|
||||
}
|
||||
|
||||
removeHeader(name: string) {
|
||||
removeHeader (name: string) {
|
||||
if (name == null) {
|
||||
throw new Error('`name` is required for removeHeader(name)');
|
||||
}
|
||||
|
||||
if (this._started || this._firstWrite) {
|
||||
throw new Error("Can't remove headers after they are sent");
|
||||
throw new Error('Can\'t remove headers after they are sent');
|
||||
}
|
||||
|
||||
const key = name.toLowerCase();
|
||||
delete this._urlLoaderOptions.headers[key];
|
||||
}
|
||||
|
||||
_write(chunk: Buffer, encoding: BufferEncoding, callback: () => void) {
|
||||
_write (chunk: Buffer, encoding: BufferEncoding, callback: () => void) {
|
||||
this._firstWrite = true;
|
||||
if (!this._body) {
|
||||
this._body = new SlurpStream();
|
||||
@@ -421,7 +412,7 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
|
||||
this._body.write(chunk, encoding, callback);
|
||||
}
|
||||
|
||||
_final(callback: () => void) {
|
||||
_final (callback: () => void) {
|
||||
if (this._body) {
|
||||
// TODO: is this the right way to forward to another stream?
|
||||
this._body.end(callback);
|
||||
@@ -432,9 +423,9 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
|
||||
}
|
||||
}
|
||||
|
||||
_startRequest() {
|
||||
_startRequest () {
|
||||
this._started = true;
|
||||
const stringifyValues = (obj: Record<string, { name: string; value: string | string[] }>) => {
|
||||
const stringifyValues = (obj: Record<string, { name: string, value: string | string[] }>) => {
|
||||
const ret: Record<string, string> = {};
|
||||
for (const { name, value } of Object.values(obj)) {
|
||||
ret[name] = value.toString();
|
||||
@@ -449,16 +440,14 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
|
||||
const opts = { ...this._urlLoaderOptions, extraHeaders: stringifyValues(this._urlLoaderOptions.headers) };
|
||||
this._urlLoader = createURLLoader(opts);
|
||||
this._urlLoader.on('response-started', (event, finalUrl, responseHead) => {
|
||||
const response = (this._response = new IncomingMessage(responseHead));
|
||||
const response = this._response = new IncomingMessage(responseHead);
|
||||
this.emit('response', response);
|
||||
});
|
||||
this._urlLoader.on('data', (event, data, resume) => {
|
||||
this._response!._storeInternalData(Buffer.from(data), resume);
|
||||
});
|
||||
this._urlLoader.on('complete', () => {
|
||||
if (this._response) {
|
||||
this._response._storeInternalData(null, null);
|
||||
}
|
||||
if (this._response) { this._response._storeInternalData(null, null); }
|
||||
});
|
||||
this._urlLoader.on('error', (event, netErrorString) => {
|
||||
const error = new Error(netErrorString);
|
||||
@@ -477,12 +466,10 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
|
||||
this._urlLoader.on('redirect', (event, redirectInfo, headers) => {
|
||||
const { statusCode, newMethod, newUrl } = redirectInfo;
|
||||
if (this._redirectPolicy === 'error') {
|
||||
this._die(new Error("Attempted to redirect, but redirect policy was 'error'"));
|
||||
this._die(new Error('Attempted to redirect, but redirect policy was \'error\''));
|
||||
} else if (this._redirectPolicy === 'manual') {
|
||||
let _followRedirect = false;
|
||||
this._followRedirectCb = () => {
|
||||
_followRedirect = true;
|
||||
};
|
||||
this._followRedirectCb = () => { _followRedirect = true; };
|
||||
try {
|
||||
this.emit('redirect', statusCode, newMethod, newUrl, headers);
|
||||
} finally {
|
||||
@@ -518,7 +505,7 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
|
||||
});
|
||||
}
|
||||
|
||||
followRedirect() {
|
||||
followRedirect () {
|
||||
if (this._followRedirectCb) {
|
||||
this._followRedirectCb();
|
||||
} else {
|
||||
@@ -526,17 +513,15 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
|
||||
}
|
||||
}
|
||||
|
||||
abort() {
|
||||
abort () {
|
||||
if (!this._aborted) {
|
||||
process.nextTick(() => {
|
||||
this.emit('abort');
|
||||
});
|
||||
process.nextTick(() => { this.emit('abort'); });
|
||||
}
|
||||
this._aborted = true;
|
||||
this._die();
|
||||
}
|
||||
|
||||
_die(err?: Error) {
|
||||
_die (err?: Error) {
|
||||
// Node.js assumes that any stream which is ended is no longer capable of emitted events
|
||||
// which is a faulty assumption for the case of an object that is acting like a stream
|
||||
// (our urlRequest). If we don't emit here, this causes errors since we *do* expect
|
||||
@@ -552,7 +537,7 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
|
||||
}
|
||||
}
|
||||
|
||||
getUploadProgress(): UploadProgress {
|
||||
getUploadProgress (): UploadProgress {
|
||||
return this._uploadProgress ? { ...this._uploadProgress } : { active: false, started: false, current: 0, total: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ const handleESModule = (loader: ElectronInternal.ModuleLoader) => () => {
|
||||
};
|
||||
|
||||
// Attaches properties to |targetExports|.
|
||||
export function defineProperties(targetExports: Object, moduleList: ElectronInternal.ModuleEntry[]) {
|
||||
export function defineProperties (targetExports: Object, moduleList: ElectronInternal.ModuleEntry[]) {
|
||||
const descriptors: PropertyDescriptorMap = {};
|
||||
for (const module of moduleList) {
|
||||
descriptors[module.name] = {
|
||||
|
||||
@@ -2,15 +2,13 @@ type DeprecationHandler = (message: string) => void;
|
||||
|
||||
let deprecationHandler: DeprecationHandler | null = null;
|
||||
|
||||
export function warnOnce(oldName: string, newName?: string) {
|
||||
return warnOnceMessage(
|
||||
newName
|
||||
? `'${oldName}' is deprecated and will be removed. Please use '${newName}' instead.`
|
||||
: `'${oldName}' is deprecated and will be removed.`
|
||||
);
|
||||
export function warnOnce (oldName: string, newName?: string) {
|
||||
return warnOnceMessage(newName
|
||||
? `'${oldName}' is deprecated and will be removed. Please use '${newName}' instead.`
|
||||
: `'${oldName}' is deprecated and will be removed.`);
|
||||
}
|
||||
|
||||
export function warnOnceMessage(msg: string) {
|
||||
export function warnOnceMessage (msg: string) {
|
||||
let warned = false;
|
||||
return () => {
|
||||
if (!warned && !process.noDeprecation) {
|
||||
@@ -20,21 +18,21 @@ export function warnOnceMessage(msg: string) {
|
||||
};
|
||||
}
|
||||
|
||||
export function setHandler(handler: DeprecationHandler | null): void {
|
||||
export function setHandler (handler: DeprecationHandler | null): void {
|
||||
deprecationHandler = handler;
|
||||
}
|
||||
|
||||
export function getHandler(): DeprecationHandler | null {
|
||||
export function getHandler (): DeprecationHandler | null {
|
||||
return deprecationHandler;
|
||||
}
|
||||
|
||||
export function warn(oldName: string, newName: string): void {
|
||||
export function warn (oldName: string, newName: string): void {
|
||||
if (!process.noDeprecation) {
|
||||
log(`'${oldName}' is deprecated. Use '${newName}' instead.`);
|
||||
}
|
||||
}
|
||||
|
||||
export function log(message: string): void {
|
||||
export function log (message: string): void {
|
||||
if (typeof deprecationHandler === 'function') {
|
||||
deprecationHandler(message);
|
||||
} else if (process.throwDeprecation) {
|
||||
@@ -47,10 +45,8 @@ export function log(message: string): void {
|
||||
}
|
||||
|
||||
// remove a function with no replacement
|
||||
export function removeFunction<T extends Function>(fn: T, removedName: string): T {
|
||||
if (!fn) {
|
||||
throw new Error(`'${removedName} function' is invalid or does not exist.`);
|
||||
}
|
||||
export function removeFunction<T extends Function> (fn: T, removedName: string): T {
|
||||
if (!fn) { throw new Error(`'${removedName} function' is invalid or does not exist.`); }
|
||||
|
||||
// wrap the deprecated function to warn user
|
||||
const warn = warnOnce(`${fn.name} function`);
|
||||
@@ -61,7 +57,7 @@ export function removeFunction<T extends Function>(fn: T, removedName: string):
|
||||
}
|
||||
|
||||
// change the name of a function
|
||||
export function renameFunction<T extends Function>(fn: T, newName: string): T {
|
||||
export function renameFunction<T extends Function> (fn: T, newName: string): T {
|
||||
const warn = warnOnce(`${fn.name} function`, `${newName} function`);
|
||||
return function (this: any) {
|
||||
warn();
|
||||
@@ -70,12 +66,7 @@ export function renameFunction<T extends Function>(fn: T, newName: string): T {
|
||||
}
|
||||
|
||||
// change the name of an event
|
||||
export function event(
|
||||
emitter: NodeJS.EventEmitter,
|
||||
oldName: string,
|
||||
newName: string,
|
||||
transformer: (...args: any[]) => any[] | undefined = (...args) => args
|
||||
) {
|
||||
export function event (emitter: NodeJS.EventEmitter, oldName: string, newName: string, transformer: (...args: any[]) => any[] | undefined = (...args) => args) {
|
||||
const warn = newName.startsWith('-') /* internal event */
|
||||
? warnOnce(`${oldName} event`)
|
||||
: warnOnce(`${oldName} event`, `${newName} event`);
|
||||
@@ -91,11 +82,7 @@ export function event(
|
||||
}
|
||||
|
||||
// remove a property with no replacement
|
||||
export function removeProperty<T extends Object, K extends keyof T & string>(
|
||||
object: T,
|
||||
removedName: K,
|
||||
onlyForValues?: any[]
|
||||
): T {
|
||||
export function removeProperty<T extends Object, K extends (keyof T & string)>(object: T, removedName: K, onlyForValues?: any[]): T {
|
||||
// if the property's already been removed, warn about it
|
||||
// eslint-disable-next-line no-proto
|
||||
const info = Object.getOwnPropertyDescriptor((object as any).__proto__, removedName);
|
||||
@@ -116,7 +103,7 @@ export function removeProperty<T extends Object, K extends keyof T & string>(
|
||||
warn();
|
||||
return info.get!.call(object);
|
||||
},
|
||||
set: (newVal) => {
|
||||
set: newVal => {
|
||||
if (!onlyForValues || onlyForValues.includes(newVal)) {
|
||||
warn();
|
||||
}
|
||||
@@ -126,16 +113,12 @@ export function removeProperty<T extends Object, K extends keyof T & string>(
|
||||
}
|
||||
|
||||
// change the name of a property
|
||||
export function renameProperty<T extends Object, K extends keyof T & string>(
|
||||
object: T,
|
||||
oldName: string,
|
||||
newName: K
|
||||
): T {
|
||||
export function renameProperty<T extends Object, K extends (keyof T & string)>(object: T, oldName: string, newName: K): T {
|
||||
const warn = warnOnce(oldName, newName);
|
||||
|
||||
// if the new property isn't there yet,
|
||||
// inject it and warn about it
|
||||
if (oldName in object && !(newName in object)) {
|
||||
if ((oldName in object) && !(newName in object)) {
|
||||
warn();
|
||||
object[newName] = (object as any)[oldName];
|
||||
}
|
||||
@@ -147,14 +130,14 @@ export function renameProperty<T extends Object, K extends keyof T & string>(
|
||||
warn();
|
||||
return object[newName];
|
||||
},
|
||||
set: (value) => {
|
||||
set: value => {
|
||||
warn();
|
||||
object[newName] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function moveAPI<T extends Function>(fn: T, oldUsage: string, newUsage: string): T {
|
||||
export function moveAPI<T extends Function> (fn: T, oldUsage: string, newUsage: string): T {
|
||||
const warn = warnOnce(oldUsage, newUsage);
|
||||
return function (this: any) {
|
||||
warn();
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as util from 'util';
|
||||
|
||||
import type * as stream from 'stream';
|
||||
|
||||
type AnyFn = (...args: any[]) => any;
|
||||
type AnyFn = (...args: any[]) => any
|
||||
|
||||
// setImmediate and process.nextTick makes use of uv_check and uv_prepare to
|
||||
// run the callbacks, however since we only run uv loop on requests, the
|
||||
@@ -11,7 +11,7 @@ type AnyFn = (...args: any[]) => any;
|
||||
// which would delay the callbacks for arbitrary long time. So we should
|
||||
// initiatively activate the uv loop once setImmediate and process.nextTick is
|
||||
// called.
|
||||
const wrapWithActivateUvLoop = function <T extends AnyFn>(func: T): T {
|
||||
const wrapWithActivateUvLoop = function <T extends AnyFn> (func: T): T {
|
||||
return wrap(func, function (func) {
|
||||
return function (this: any, ...args: any[]) {
|
||||
process.activateUvLoop();
|
||||
@@ -26,7 +26,7 @@ const wrapWithActivateUvLoop = function <T extends AnyFn>(func: T): T {
|
||||
*
|
||||
* Refs: https://github.com/Microsoft/TypeScript/issues/1863
|
||||
*/
|
||||
function wrap<T extends AnyFn>(func: T, wrapper: (fn: AnyFn) => T) {
|
||||
function wrap <T extends AnyFn> (func: T, wrapper: (fn: AnyFn) => T) {
|
||||
const wrapped = wrapper(func);
|
||||
if ((func as any)[util.promisify.custom]) {
|
||||
(wrapped as any)[util.promisify.custom] = wrapper((func as any)[util.promisify.custom]);
|
||||
@@ -55,7 +55,8 @@ timers.setInterval = wrapWithActivateUvLoop(timers.setInterval);
|
||||
// only in the process that runs node event loop alongside chromium
|
||||
// event loop. We skip renderer with nodeIntegration here because node globals
|
||||
// are deleted in these processes, see renderer/init.js for reference.
|
||||
if (process.type === 'browser' || process.type === 'utility') {
|
||||
if (process.type === 'browser' ||
|
||||
process.type === 'utility') {
|
||||
global.setTimeout = timers.setTimeout;
|
||||
global.setInterval = timers.setInterval;
|
||||
}
|
||||
@@ -68,7 +69,7 @@ if (process.platform === 'win32') {
|
||||
Object.defineProperty(process, 'stdin', {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
get() {
|
||||
get () {
|
||||
return stdin;
|
||||
}
|
||||
});
|
||||
@@ -107,11 +108,7 @@ const originalResolveFilename = Module._resolveFilename;
|
||||
// renderer process regardless of the names, they're superficial for TypeScript
|
||||
// only.
|
||||
const electronModuleNames = new Set([
|
||||
'electron',
|
||||
'electron/main',
|
||||
'electron/renderer',
|
||||
'electron/common',
|
||||
'electron/utility'
|
||||
'electron', 'electron/main', 'electron/renderer', 'electron/common', 'electron/utility'
|
||||
]);
|
||||
Module._resolveFilename = function (request, parent, isMain, options) {
|
||||
if (electronModuleNames.has(request)) {
|
||||
|
||||
@@ -27,5 +27,5 @@ export const enum IPC_MESSAGES {
|
||||
INSPECTOR_SELECT_FILE = 'INSPECTOR_SELECT_FILE',
|
||||
|
||||
IMPORT_SHARED_TEXTURE_TRANSFER_MAIN_TO_RENDERER = 'IMPORT_SHARED_TEXTURE_TRANSFER_MAIN_TO_RENDERER',
|
||||
IMPORT_SHARED_TEXTURE_RELEASE_RENDERER_TO_MAIN = 'IMPORT_SHARED_TEXTURE_RELEASE_RENDERER_TO_MAIN'
|
||||
IMPORT_SHARED_TEXTURE_RELEASE_RENDERER_TO_MAIN = 'IMPORT_SHARED_TEXTURE_RELEASE_RENDERER_TO_MAIN',
|
||||
}
|
||||
|
||||
@@ -14,18 +14,15 @@ class Timeout {
|
||||
_id: ReturnType<typeof globalThis.setTimeout>;
|
||||
_clearFn: (id: ReturnType<typeof globalThis.setTimeout>) => void;
|
||||
|
||||
constructor(
|
||||
id: ReturnType<typeof globalThis.setTimeout>,
|
||||
clearFn: (id: ReturnType<typeof globalThis.setTimeout>) => void
|
||||
) {
|
||||
constructor (id: ReturnType<typeof globalThis.setTimeout>, clearFn: (id: ReturnType<typeof globalThis.setTimeout>) => void) {
|
||||
this._id = id;
|
||||
this._clearFn = clearFn;
|
||||
}
|
||||
|
||||
unref() {}
|
||||
ref() {}
|
||||
unref () {}
|
||||
ref () {}
|
||||
|
||||
close() {
|
||||
close () {
|
||||
this._clearFn.call(globalThis, this._id);
|
||||
}
|
||||
}
|
||||
@@ -67,7 +64,7 @@ export const active = function (item: EnrollableItem) {
|
||||
|
||||
const msecs = item._idleTimeout;
|
||||
if (msecs !== undefined && msecs >= 0) {
|
||||
item._idleTimeoutId = setTimeout(function onTimeout() {
|
||||
item._idleTimeoutId = setTimeout(function onTimeout () {
|
||||
if (item._onTimeout) item._onTimeout();
|
||||
}, msecs);
|
||||
}
|
||||
@@ -83,23 +80,23 @@ const clearImmediateFallback = function (id: number) {
|
||||
delete immediateIds[id];
|
||||
};
|
||||
|
||||
export const setImmediate =
|
||||
typeof globalThis.setImmediate === 'function'
|
||||
? globalThis.setImmediate
|
||||
: function (fn: (...args: unknown[]) => void, ...rest: unknown[]) {
|
||||
const id = nextImmediateId++;
|
||||
export const setImmediate = typeof globalThis.setImmediate === 'function'
|
||||
? globalThis.setImmediate
|
||||
: function (fn: (...args: unknown[]) => void, ...rest: unknown[]) {
|
||||
const id = nextImmediateId++;
|
||||
|
||||
immediateIds[id] = true;
|
||||
immediateIds[id] = true;
|
||||
|
||||
Promise.resolve().then(function onMicrotask() {
|
||||
if (immediateIds[id]) {
|
||||
fn(...rest);
|
||||
clearImmediateFallback(id);
|
||||
}
|
||||
});
|
||||
Promise.resolve().then(function onMicrotask () {
|
||||
if (immediateIds[id]) {
|
||||
fn(...rest);
|
||||
clearImmediateFallback(id);
|
||||
}
|
||||
});
|
||||
|
||||
return id;
|
||||
};
|
||||
return id;
|
||||
};
|
||||
|
||||
export const clearImmediate =
|
||||
typeof globalThis.clearImmediate === 'function' ? globalThis.clearImmediate : clearImmediateFallback;
|
||||
export const clearImmediate = typeof globalThis.clearImmediate === 'function'
|
||||
? globalThis.clearImmediate
|
||||
: clearImmediateFallback;
|
||||
|
||||
@@ -58,7 +58,13 @@ export const syncMethods = new Set([
|
||||
...navigationHistorySyncMethods
|
||||
]);
|
||||
|
||||
export const properties = new Set(['audioMuted', 'userAgent', 'zoomLevel', 'zoomFactor', 'frameRate']);
|
||||
export const properties = new Set([
|
||||
'audioMuted',
|
||||
'userAgent',
|
||||
'zoomLevel',
|
||||
'zoomFactor',
|
||||
'frameRate'
|
||||
]);
|
||||
|
||||
export const asyncMethods = new Set([
|
||||
'capturePage',
|
||||
|
||||
@@ -11,4 +11,8 @@ const _global = typeof globalThis !== 'undefined' ? globalThis.global : (self ||
|
||||
const process = _global.process;
|
||||
const Buffer = _global.Buffer;
|
||||
|
||||
export { _global, process, Buffer };
|
||||
export {
|
||||
_global,
|
||||
process,
|
||||
Buffer
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@ declare const isolatedApi: WebViewImplHooks;
|
||||
|
||||
if (isolatedApi.guestViewInternal) {
|
||||
// Must setup the WebView element in main world.
|
||||
const { setupWebView } =
|
||||
require('@electron/internal/renderer/web-view/web-view-element') as typeof webViewElementModule;
|
||||
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element') as typeof webViewElementModule;
|
||||
setupWebView(isolatedApi);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,9 @@ const Module = require('module') as NodeJS.ModuleInternal;
|
||||
|
||||
const Promise: PromiseConstructor = global.Promise;
|
||||
|
||||
const envNoAsar = process.env.ELECTRON_NO_ASAR && process.type !== 'browser' && process.type !== 'renderer';
|
||||
const envNoAsar = process.env.ELECTRON_NO_ASAR &&
|
||||
process.type !== 'browser' &&
|
||||
process.type !== 'renderer';
|
||||
const isAsarDisabled = () => process.noAsar || envNoAsar;
|
||||
|
||||
const internalBinding = process.internalBinding!;
|
||||
@@ -46,15 +48,20 @@ process._getOrCreateArchive = getOrCreateArchive;
|
||||
|
||||
const asarRe = /\.asar/i;
|
||||
|
||||
const { getValidatedPath, getOptions, getDirent } = __non_webpack_require__(
|
||||
'internal/fs/utils'
|
||||
) as typeof import('@node/lib/internal/fs/utils');
|
||||
const {
|
||||
getValidatedPath,
|
||||
getOptions,
|
||||
getDirent
|
||||
} = __non_webpack_require__('internal/fs/utils') as typeof import('@node/lib/internal/fs/utils');
|
||||
|
||||
const { assignFunctionName } = __non_webpack_require__('internal/util') as typeof import('@node/lib/internal/util');
|
||||
const {
|
||||
assignFunctionName
|
||||
} = __non_webpack_require__('internal/util') as typeof import('@node/lib/internal/util');
|
||||
|
||||
const { validateBoolean, validateFunction } = __non_webpack_require__(
|
||||
'internal/validators'
|
||||
) as typeof import('@node/lib/internal/validators');
|
||||
const {
|
||||
validateBoolean,
|
||||
validateFunction
|
||||
} = __non_webpack_require__('internal/validators') as typeof import('@node/lib/internal/validators');
|
||||
|
||||
// In the renderer node internals use the node global URL but we do not set that to be
|
||||
// the global URL instance. We need to do instanceof checks against the internal URL impl
|
||||
@@ -87,7 +94,7 @@ const gid = process.getgid?.() ?? 0;
|
||||
|
||||
const fakeTime = new Date();
|
||||
|
||||
function getDirents(p: string, { 0: names, 1: types }: any[][]): Dirent[] {
|
||||
function getDirents (p: string, { 0: names, 1: types }: any[][]): Dirent[] {
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
let type = types[i];
|
||||
const info = splitPath(path.join(p, names[i]));
|
||||
@@ -107,7 +114,7 @@ function getDirents(p: string, { 0: names, 1: types }: any[][]): Dirent[] {
|
||||
enum AsarFileType {
|
||||
kFile = (constants as any).UV_DIRENT_FILE,
|
||||
kDirectory = (constants as any).UV_DIRENT_DIR,
|
||||
kLink = (constants as any).UV_DIRENT_LINK
|
||||
kLink = (constants as any).UV_DIRENT_LINK,
|
||||
}
|
||||
|
||||
const fileTypeToMode = new Map<AsarFileType, number>([
|
||||
@@ -119,8 +126,7 @@ const fileTypeToMode = new Map<AsarFileType, number>([
|
||||
const asarStatsToFsStats = function (stats: NodeJS.AsarFileStat) {
|
||||
const { Stats } = require('fs');
|
||||
|
||||
const mode =
|
||||
constants.S_IROTH | constants.S_IRGRP | constants.S_IRUSR | constants.S_IWUSR | fileTypeToMode.get(stats.type)!;
|
||||
const mode = constants.S_IROTH | constants.S_IRGRP | constants.S_IRUSR | constants.S_IWUSR | fileTypeToMode.get(stats.type)!;
|
||||
|
||||
return new Stats(
|
||||
1, // dev
|
||||
@@ -147,9 +153,9 @@ const enum AsarError {
|
||||
INVALID_ARCHIVE = 'INVALID_ARCHIVE'
|
||||
}
|
||||
|
||||
type AsarErrorObject = Error & { code?: string; errno?: number };
|
||||
type AsarErrorObject = Error & { code?: string, errno?: number };
|
||||
|
||||
const createError = (errorType: AsarError, { asarPath, filePath }: { asarPath?: string; filePath?: string } = {}) => {
|
||||
const createError = (errorType: AsarError, { asarPath, filePath }: { asarPath?: string, filePath?: string } = {}) => {
|
||||
let error: AsarErrorObject;
|
||||
switch (errorType) {
|
||||
case AsarError.NOT_FOUND:
|
||||
@@ -176,12 +182,7 @@ const createError = (errorType: AsarError, { asarPath, filePath }: { asarPath?:
|
||||
return error;
|
||||
};
|
||||
|
||||
const overrideAPISync = function (
|
||||
module: Record<string, any>,
|
||||
name: string,
|
||||
pathArgumentIndex?: number | null,
|
||||
fromAsync: boolean = false
|
||||
) {
|
||||
const overrideAPISync = function (module: Record<string, any>, name: string, pathArgumentIndex?: number | null, fromAsync: boolean = false) {
|
||||
if (pathArgumentIndex == null) pathArgumentIndex = 0;
|
||||
const old = module[name];
|
||||
const func = function (this: any, ...args: any[]) {
|
||||
@@ -250,7 +251,7 @@ const overrideAPI = function (module: Record<string, any>, name: string, pathArg
|
||||
};
|
||||
|
||||
let crypto: typeof Crypto;
|
||||
function validateBufferIntegrity(buffer: Buffer, integrity: NodeJS.AsarFileInfo['integrity']) {
|
||||
function validateBufferIntegrity (buffer: Buffer, integrity: NodeJS.AsarFileInfo['integrity']) {
|
||||
if (!integrity) return;
|
||||
|
||||
// Delay load crypto to improve app boot performance
|
||||
@@ -316,7 +317,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
if (!archive) {
|
||||
if (shouldThrowStatError(options)) {
|
||||
throw createError(AsarError.INVALID_ARCHIVE, { asarPath });
|
||||
}
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -324,7 +325,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
if (!stats) {
|
||||
if (shouldThrowStatError(options)) {
|
||||
throw createError(AsarError.NOT_FOUND, { asarPath, filePath });
|
||||
}
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -452,7 +453,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
fs.promises.realpath = util.promisify(fs.realpath.native);
|
||||
|
||||
const { exists: nativeExists } = fs;
|
||||
fs.exists = function exists(pathArgument: string, callback: any) {
|
||||
fs.exists = function exists (pathArgument: string, callback: any) {
|
||||
let pathInfo: ReturnType<typeof splitPath>;
|
||||
try {
|
||||
pathInfo = splitPath(pathArgument);
|
||||
@@ -470,11 +471,11 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const pathExists = archive.stat(filePath) !== false;
|
||||
const pathExists = (archive.stat(filePath) !== false);
|
||||
nextTick(callback, [pathExists]);
|
||||
};
|
||||
|
||||
fs.exists[util.promisify.custom] = function exists(pathArgument: string) {
|
||||
fs.exists[util.promisify.custom] = function exists (pathArgument: string) {
|
||||
const pathInfo = splitPath(pathArgument);
|
||||
if (!pathInfo.isAsar) return nativeExists[util.promisify.custom](pathArgument);
|
||||
const { asarPath, filePath } = pathInfo;
|
||||
@@ -595,7 +596,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
}
|
||||
};
|
||||
|
||||
function fsReadFileAsar(pathArgument: string, options: any, callback: any) {
|
||||
function fsReadFileAsar (pathArgument: string, options: any, callback: any) {
|
||||
const pathInfo = splitPath(pathArgument);
|
||||
if (pathInfo.isAsar) {
|
||||
const { asarPath, filePath } = pathInfo;
|
||||
@@ -673,7 +674,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
return p(pathArgument, options);
|
||||
};
|
||||
|
||||
function readFileFromArchiveSync(
|
||||
function readFileFromArchiveSync (
|
||||
pathInfo: { asarPath: string; filePath: string },
|
||||
options: any
|
||||
): ReturnType<typeof readFileSync> {
|
||||
@@ -685,7 +686,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
const info = archive.getFileInfo(filePath);
|
||||
if (!info) throw createError(AsarError.NOT_FOUND, { asarPath, filePath });
|
||||
|
||||
if (info.size === 0) return options ? '' : Buffer.alloc(0);
|
||||
if (info.size === 0) return (options) ? '' : Buffer.alloc(0);
|
||||
if (info.unpacked) {
|
||||
const realPath = archive.copyFileOut(filePath);
|
||||
return fs.readFileSync(realPath, options);
|
||||
@@ -709,7 +710,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
logASARAccess(asarPath, filePath, info.offset);
|
||||
fs.readSync(fd, buffer, 0, info.size, info.offset);
|
||||
validateBufferIntegrity(buffer, info.integrity);
|
||||
return encoding ? buffer.toString(encoding) : buffer;
|
||||
return (encoding) ? buffer.toString(encoding) : buffer;
|
||||
}
|
||||
|
||||
const { readFileSync } = fs;
|
||||
@@ -720,16 +721,12 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
return readFileFromArchiveSync(pathInfo, options);
|
||||
};
|
||||
|
||||
type ReaddirOptions =
|
||||
| { encoding: BufferEncoding | null; withFileTypes?: false; recursive?: false }
|
||||
| undefined
|
||||
| null;
|
||||
type ReaddirOptions = { encoding: BufferEncoding | null; withFileTypes?: false, recursive?: false } | undefined | null;
|
||||
type ReaddirCallback = (err: NodeJS.ErrnoException | null, files?: string[]) => void;
|
||||
|
||||
const processReaddirResult = (args: any) =>
|
||||
args.context.withFileTypes ? handleDirents(args) : handleFilePaths(args);
|
||||
const processReaddirResult = (args: any) => (args.context.withFileTypes ? handleDirents(args) : handleFilePaths(args));
|
||||
|
||||
function handleDirents({ result, currentPath, context }: { result: any[]; currentPath: string; context: any }) {
|
||||
function handleDirents ({ result, currentPath, context }: { result: any[], currentPath: string, context: any }) {
|
||||
const length = result[0].length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
const resultPath = path.join(currentPath, result[0][i]);
|
||||
@@ -754,7 +751,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
}
|
||||
}
|
||||
|
||||
function handleFilePaths({ result, currentPath, context }: { result: string[]; currentPath: string; context: any }) {
|
||||
function handleFilePaths ({ result, currentPath, context }: { result: string[], currentPath: string, context: any }) {
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
const resultPath = path.join(currentPath, result[i]);
|
||||
const relativeResultPath = path.relative(context.basePath, resultPath);
|
||||
@@ -767,7 +764,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
}
|
||||
}
|
||||
|
||||
function readdirRecursive(basePath: string, options: ReaddirOptions, callback: ReaddirCallback) {
|
||||
function readdirRecursive (basePath: string, options: ReaddirOptions, callback: ReaddirCallback) {
|
||||
const context = {
|
||||
withFileTypes: Boolean(options!.withFileTypes),
|
||||
encoding: options!.encoding,
|
||||
@@ -778,7 +775,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
|
||||
let i = 0;
|
||||
|
||||
function read(pathArg: string) {
|
||||
function read (pathArg: string) {
|
||||
const req = new binding.FSReqCallback();
|
||||
req.oncomplete = (err: any, result: string) => {
|
||||
if (err) {
|
||||
@@ -827,8 +824,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
// native call to readdir withFileTypes i.e. an array of arrays.
|
||||
if (context.withFileTypes) {
|
||||
readdirResult = [
|
||||
[...readdirResult],
|
||||
readdirResult.map((p: string) => {
|
||||
[...readdirResult], readdirResult.map((p: string) => {
|
||||
return internalBinding('fs').internalModuleStat(path.join(pathArg, p));
|
||||
})
|
||||
];
|
||||
@@ -846,7 +842,12 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
callback(null, context.readdirResults);
|
||||
}
|
||||
} else {
|
||||
binding.readdir(pathArg, context.encoding, context.withFileTypes, req);
|
||||
binding.readdir(
|
||||
pathArg,
|
||||
context.encoding,
|
||||
context.withFileTypes,
|
||||
req
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1022,11 +1023,11 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
const stats = archive.stat(filePath);
|
||||
if (!stats) return -34;
|
||||
|
||||
return stats.type === AsarFileType.kDirectory ? 1 : 0;
|
||||
return (stats.type === AsarFileType.kDirectory) ? 1 : 0;
|
||||
};
|
||||
|
||||
const { kUsePromises } = binding;
|
||||
async function readdirRecursivePromises(originalPath: string, options: ReaddirOptions) {
|
||||
async function readdirRecursivePromises (originalPath: string, options: ReaddirOptions) {
|
||||
const result: any[] = [];
|
||||
|
||||
const pathInfo = splitPath(originalPath);
|
||||
@@ -1045,8 +1046,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
initialItem = files;
|
||||
if (withFileTypes) {
|
||||
initialItem = [
|
||||
[...initialItem],
|
||||
initialItem.map((p: string) => {
|
||||
[...initialItem], initialItem.map((p: string) => {
|
||||
return internalBinding('fs').internalModuleStat(path.join(originalPath, p));
|
||||
})
|
||||
];
|
||||
@@ -1079,13 +1079,17 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
if (!files) continue;
|
||||
|
||||
readdirResult = [
|
||||
[...files],
|
||||
files.map((p: string) => {
|
||||
[...files], files.map((p: string) => {
|
||||
return internalBinding('fs').internalModuleStat(path.join(direntPath, p));
|
||||
})
|
||||
];
|
||||
} else {
|
||||
readdirResult = await binding.readdir(direntPath, options!.encoding, true, kUsePromises);
|
||||
readdirResult = await binding.readdir(
|
||||
direntPath,
|
||||
options!.encoding,
|
||||
true,
|
||||
kUsePromises
|
||||
);
|
||||
}
|
||||
queue.push([direntPath, readdirResult]);
|
||||
}
|
||||
@@ -1110,7 +1114,12 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
if (!files) return result;
|
||||
item = files;
|
||||
} else {
|
||||
item = await binding.readdir(path.toNamespacedPath(direntPath), options!.encoding, false, kUsePromises);
|
||||
item = await binding.readdir(
|
||||
path.toNamespacedPath(direntPath),
|
||||
options!.encoding,
|
||||
false,
|
||||
kUsePromises
|
||||
);
|
||||
}
|
||||
queue.push([direntPath, item]);
|
||||
}
|
||||
@@ -1121,7 +1130,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
return result;
|
||||
}
|
||||
|
||||
function readdirSyncRecursive(basePath: string, options: ReaddirOptions) {
|
||||
function readdirSyncRecursive (basePath: string, options: ReaddirOptions) {
|
||||
const context = {
|
||||
withFileTypes: Boolean(options!.withFileTypes),
|
||||
encoding: options!.encoding,
|
||||
@@ -1130,7 +1139,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
pathsQueue: [basePath]
|
||||
};
|
||||
|
||||
function read(pathArg: string) {
|
||||
function read (pathArg: string) {
|
||||
let readdirResult;
|
||||
|
||||
const pathInfo = splitPath(pathArg);
|
||||
@@ -1145,14 +1154,17 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
// native call to readdir withFileTypes i.e. an array of arrays.
|
||||
if (context.withFileTypes) {
|
||||
readdirResult = [
|
||||
[...readdirResult],
|
||||
readdirResult.map((p: string) => {
|
||||
[...readdirResult], readdirResult.map((p: string) => {
|
||||
return internalBinding('fs').internalModuleStat(path.join(pathArg, p));
|
||||
})
|
||||
];
|
||||
}
|
||||
} else {
|
||||
readdirResult = binding.readdir(path.toNamespacedPath(pathArg), context.encoding, context.withFileTypes);
|
||||
readdirResult = binding.readdir(
|
||||
path.toNamespacedPath(pathArg),
|
||||
context.encoding,
|
||||
context.withFileTypes
|
||||
);
|
||||
}
|
||||
|
||||
if (readdirResult === undefined) {
|
||||
@@ -1203,7 +1215,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
};
|
||||
}
|
||||
|
||||
function invokeWithNoAsar(func: Function) {
|
||||
function invokeWithNoAsar (func: Function) {
|
||||
return function (this: any) {
|
||||
const processNoAsarOriginalValue = process.noAsar;
|
||||
process.noAsar = true;
|
||||
@@ -1235,10 +1247,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
// command as a single path to an archive.
|
||||
const { exec, execSync } = childProcess;
|
||||
childProcess.exec = invokeWithNoAsar(exec);
|
||||
childProcess.exec[util.promisify.custom] = assignFunctionName(
|
||||
'exec',
|
||||
invokeWithNoAsar(exec[util.promisify.custom])
|
||||
);
|
||||
childProcess.exec[util.promisify.custom] = assignFunctionName('exec', invokeWithNoAsar(exec[util.promisify.custom]));
|
||||
childProcess.execSync = invokeWithNoAsar(execSync);
|
||||
|
||||
overrideAPI(childProcess, 'execFile');
|
||||
|
||||
@@ -25,11 +25,8 @@ cp.fork = (modulePath, args?, options?: cp.ForkOptions) => {
|
||||
args = [];
|
||||
}
|
||||
// Fallback to original fork to report arg type errors.
|
||||
if (
|
||||
typeof modulePath !== 'string' ||
|
||||
!Array.isArray(args) ||
|
||||
(typeof options !== 'object' && typeof options !== 'undefined')
|
||||
) {
|
||||
if (typeof modulePath !== 'string' || !Array.isArray(args) ||
|
||||
(typeof options !== 'object' && typeof options !== 'undefined')) {
|
||||
return originalFork(modulePath, args, options);
|
||||
}
|
||||
// When forking a child script, we setup a special environment to make
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
import '@electron/internal/sandboxed_renderer/pre-init';
|
||||
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
|
||||
import type * as ipcRendererUtilsModule from '@electron/internal/renderer/ipc-renderer-internal-utils';
|
||||
import {
|
||||
createPreloadProcessObject,
|
||||
executeSandboxedPreloadScripts
|
||||
} from '@electron/internal/sandboxed_renderer/preload';
|
||||
import { createPreloadProcessObject, executeSandboxedPreloadScripts } from '@electron/internal/sandboxed_renderer/preload';
|
||||
|
||||
import * as events from 'events';
|
||||
|
||||
declare const binding: {
|
||||
get: (name: string) => any;
|
||||
process: NodeJS.Process;
|
||||
createPreloadScript: (src: string) => Function;
|
||||
createPreloadScript: (src: string) => Function
|
||||
};
|
||||
|
||||
const ipcRendererUtils =
|
||||
require('@electron/internal/renderer/ipc-renderer-internal-utils') as typeof ipcRendererUtilsModule;
|
||||
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils') as typeof ipcRendererUtilsModule;
|
||||
|
||||
const { preloadScripts, process: processProps } = ipcRendererUtils.invokeSync<{
|
||||
const {
|
||||
preloadScripts,
|
||||
process: processProps
|
||||
} = ipcRendererUtils.invokeSync<{
|
||||
preloadScripts: ElectronInternal.PreloadScript[];
|
||||
process: NodeJS.Process;
|
||||
}>(IPC_MESSAGES.BROWSER_SANDBOX_LOAD);
|
||||
@@ -45,18 +44,15 @@ Object.assign(process, processProps);
|
||||
|
||||
require('@electron/internal/renderer/ipc-native-setup');
|
||||
|
||||
executeSandboxedPreloadScripts(
|
||||
{
|
||||
loadedModules,
|
||||
loadableModules,
|
||||
process: preloadProcess,
|
||||
createPreloadScript: binding.createPreloadScript,
|
||||
exposeGlobals: {
|
||||
Buffer,
|
||||
// FIXME(samuelmaddock): workaround webpack bug replacing this with just
|
||||
// `__webpack_require__.g,` which causes script error
|
||||
global: globalThis
|
||||
}
|
||||
},
|
||||
preloadScripts
|
||||
);
|
||||
executeSandboxedPreloadScripts({
|
||||
loadedModules,
|
||||
loadableModules,
|
||||
process: preloadProcess,
|
||||
createPreloadScript: binding.createPreloadScript,
|
||||
exposeGlobals: {
|
||||
Buffer,
|
||||
// FIXME(samuelmaddock): workaround webpack bug replacing this with just
|
||||
// `__webpack_require__.g,` which causes script error
|
||||
global: globalThis
|
||||
}
|
||||
}, preloadScripts);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
const binding = process._linkedBinding('electron_renderer_crash_reporter');
|
||||
|
||||
export default {
|
||||
addExtraParameter(key: string, value: string) {
|
||||
addExtraParameter (key: string, value: string) {
|
||||
binding.addExtraParameter(key, value);
|
||||
},
|
||||
|
||||
removeExtraParameter(key: string) {
|
||||
removeExtraParameter (key: string) {
|
||||
binding.removeExtraParameter(key);
|
||||
},
|
||||
|
||||
getParameters() {
|
||||
getParameters () {
|
||||
return binding.getParameters();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,19 +6,19 @@ const ipc = getIPCRenderer();
|
||||
const internal = false;
|
||||
|
||||
class IpcRenderer extends EventEmitter implements Electron.IpcRenderer {
|
||||
send(channel: string, ...args: any[]) {
|
||||
send (channel: string, ...args: any[]) {
|
||||
return ipc.send(internal, channel, args);
|
||||
}
|
||||
|
||||
sendSync(channel: string, ...args: any[]) {
|
||||
sendSync (channel: string, ...args: any[]) {
|
||||
return ipc.sendSync(internal, channel, args);
|
||||
}
|
||||
|
||||
sendToHost(channel: string, ...args: any[]) {
|
||||
sendToHost (channel: string, ...args: any[]) {
|
||||
return ipc.sendToHost(channel, args);
|
||||
}
|
||||
|
||||
async invoke(channel: string, ...args: any[]) {
|
||||
async invoke (channel: string, ...args: any[]) {
|
||||
const { error, result } = await ipc.invoke(internal, channel, args);
|
||||
if (error) {
|
||||
throw new Error(`Error invoking remote method '${channel}': ${error}`);
|
||||
@@ -26,7 +26,7 @@ class IpcRenderer extends EventEmitter implements Electron.IpcRenderer {
|
||||
return result;
|
||||
}
|
||||
|
||||
postMessage(channel: string, message: any, transferables: any) {
|
||||
postMessage (channel: string, message: any, transferables: any) {
|
||||
return ipc.postMessage(channel, message, transferables);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,17 @@ Object.defineProperty(WebFramePrototype, 'routingId', {
|
||||
configurable: true,
|
||||
get: function (this: Electron.WebFrame) {
|
||||
routingIdDeprecated();
|
||||
return ipcRendererUtils.invokeSync<number>(IPC_MESSAGES.BROWSER_GET_FRAME_ROUTING_ID_SYNC, this.frameToken);
|
||||
return ipcRendererUtils.invokeSync<number>(
|
||||
IPC_MESSAGES.BROWSER_GET_FRAME_ROUTING_ID_SYNC,
|
||||
this.frameToken
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const findFrameByRoutingIdDeprecated = deprecate.warnOnce('webFrame.findFrameByRoutingId', 'webFrame.findFrameByToken');
|
||||
WebFramePrototype.findFrameByRoutingId = function (routingId: number): Electron.WebFrame | null {
|
||||
WebFramePrototype.findFrameByRoutingId = function (
|
||||
routingId: number
|
||||
): Electron.WebFrame | null {
|
||||
findFrameByRoutingIdDeprecated();
|
||||
const frameToken = ipcRendererUtils.invokeSync<string | undefined>(
|
||||
IPC_MESSAGES.BROWSER_GET_FRAME_TOKEN_SYNC,
|
||||
|
||||
@@ -43,7 +43,6 @@ webFrameInit();
|
||||
|
||||
// Warn about security issues
|
||||
if (process.isMainFrame) {
|
||||
const { securityWarnings } =
|
||||
require('@electron/internal/renderer/security-warnings') as typeof securityWarningsModule;
|
||||
const { securityWarnings } = require('@electron/internal/renderer/security-warnings') as typeof securityWarningsModule;
|
||||
securityWarnings(nodeIntegration);
|
||||
}
|
||||
|
||||
@@ -12,9 +12,7 @@ const Module = require('module') as NodeJS.ModuleInternal;
|
||||
const originalModuleLoad = Module._load;
|
||||
Module._load = function (request: string) {
|
||||
if (request === 'vm') {
|
||||
console.warn(
|
||||
"The vm module of Node.js is unsupported in Electron's renderer process due to incompatibilities with the Blink rendering engine. Crashes are likely and avoiding the module is highly recommended. This module may be removed in a future release."
|
||||
);
|
||||
console.warn('The vm module of Node.js is unsupported in Electron\'s renderer process due to incompatibilities with the Blink rendering engine. Crashes are likely and avoiding the module is highly recommended. This module may be removed in a future release.');
|
||||
}
|
||||
return originalModuleLoad.apply(this, arguments as any);
|
||||
};
|
||||
@@ -35,9 +33,9 @@ Module._load = function (request: string) {
|
||||
// variables to this wrapper please ensure to update that plugin as well.
|
||||
Module.wrapper = [
|
||||
'(function (exports, require, module, __filename, __dirname, process, global, Buffer) { ' +
|
||||
// By running the code in a new closure, it would be possible for the module
|
||||
// code to override "process" and "Buffer" with local variables.
|
||||
'return function (exports, require, module, __filename, __dirname) { ',
|
||||
// By running the code in a new closure, it would be possible for the module
|
||||
// code to override "process" and "Buffer" with local variables.
|
||||
'return function (exports, require, module, __filename, __dirname) { ',
|
||||
'\n}.call(this, exports, require, module, __filename, __dirname); });'
|
||||
];
|
||||
|
||||
@@ -48,10 +46,8 @@ process.argv.splice(1, 1);
|
||||
// Import common settings.
|
||||
require('@electron/internal/common/init');
|
||||
|
||||
const { ipcRendererInternal } =
|
||||
require('@electron/internal/renderer/ipc-renderer-internal') as typeof ipcRendererInternalModule;
|
||||
const ipcRendererUtils =
|
||||
require('@electron/internal/renderer/ipc-renderer-internal-utils') as typeof ipcRendererUtilsModule;
|
||||
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal') as typeof ipcRendererInternalModule;
|
||||
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils') as typeof ipcRendererUtilsModule;
|
||||
|
||||
process.getProcessMemoryInfo = () => {
|
||||
return ipcRendererInternal.invoke<Electron.ProcessMemoryInfo>(IPC_MESSAGES.BROWSER_GET_PROCESS_MEMORY_INFO);
|
||||
@@ -69,9 +65,7 @@ require('@electron/internal/renderer/common-init');
|
||||
|
||||
if (nodeIntegration) {
|
||||
// Export node bindings to global.
|
||||
const { makeRequireFunction } = __non_webpack_require__(
|
||||
'internal/modules/helpers'
|
||||
) as typeof import('@node/lib/internal/modules/helpers');
|
||||
const { makeRequireFunction } = __non_webpack_require__('internal/modules/helpers') as typeof import('@node/lib/internal/modules/helpers');
|
||||
global.module = new Module('electron/js2c/renderer_init');
|
||||
global.require = makeRequireFunction(global.module) as NodeRequire;
|
||||
|
||||
@@ -142,8 +136,8 @@ const { appCodeLoaded } = process;
|
||||
delete process.appCodeLoaded;
|
||||
|
||||
const { preloadPaths } = ipcRendererUtils.invokeSync<{ preloadPaths: string[] }>(IPC_MESSAGES.BROWSER_NONSANDBOX_LOAD);
|
||||
const cjsPreloads = preloadPaths.filter((p) => path.extname(p) !== '.mjs');
|
||||
const esmPreloads = preloadPaths.filter((p) => path.extname(p) === '.mjs');
|
||||
const cjsPreloads = preloadPaths.filter(p => path.extname(p) !== '.mjs');
|
||||
const esmPreloads = preloadPaths.filter(p => path.extname(p) === '.mjs');
|
||||
if (cjsPreloads.length) {
|
||||
// Load the preload scripts.
|
||||
for (const preloadScript of cjsPreloads) {
|
||||
@@ -158,21 +152,17 @@ if (cjsPreloads.length) {
|
||||
}
|
||||
}
|
||||
if (esmPreloads.length) {
|
||||
const { runEntryPointWithESMLoader } = __non_webpack_require__(
|
||||
'internal/modules/run_main'
|
||||
) as typeof import('@node/lib/internal/modules/run_main');
|
||||
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main') as typeof import('@node/lib/internal/modules/run_main');
|
||||
|
||||
runEntryPointWithESMLoader(async (cascadedLoader: any) => {
|
||||
// Load the preload scripts.
|
||||
for (const preloadScript of esmPreloads) {
|
||||
await cascadedLoader
|
||||
.import(pathToFileURL(preloadScript).toString(), undefined, Object.create(null))
|
||||
.catch((err: Error) => {
|
||||
console.error(`Unable to load preload script: ${preloadScript}`);
|
||||
console.error(err);
|
||||
await cascadedLoader.import(pathToFileURL(preloadScript).toString(), undefined, Object.create(null)).catch((err: Error) => {
|
||||
console.error(`Unable to load preload script: ${preloadScript}`);
|
||||
console.error(err);
|
||||
|
||||
ipcRendererInternal.send(IPC_MESSAGES.BROWSER_PRELOAD_ERROR, preloadScript, err);
|
||||
});
|
||||
ipcRendererInternal.send(IPC_MESSAGES.BROWSER_PRELOAD_ERROR, preloadScript, err);
|
||||
});
|
||||
}
|
||||
}).finally(() => appCodeLoaded!());
|
||||
} else {
|
||||
|
||||
@@ -8,14 +8,13 @@ import { webFrame } from 'electron/renderer';
|
||||
const { contextIsolationEnabled } = internalContextBridge;
|
||||
|
||||
/* Corrects for some Inspector adaptations needed in Electron.
|
||||
* 1) Use menu API to show context menu.
|
||||
*/
|
||||
* 1) Use menu API to show context menu.
|
||||
*/
|
||||
window.onload = function () {
|
||||
if (contextIsolationEnabled) {
|
||||
internalContextBridge.tryOverrideGlobalValueFromIsolatedWorld(
|
||||
['InspectorFrontendHost', 'showContextMenuAtPoint'],
|
||||
createMenu
|
||||
);
|
||||
internalContextBridge.tryOverrideGlobalValueFromIsolatedWorld([
|
||||
'InspectorFrontendHost', 'showContextMenuAtPoint'
|
||||
], createMenu);
|
||||
} else {
|
||||
window.InspectorFrontendHost!.showContextMenuAtPoint = createMenu;
|
||||
}
|
||||
@@ -27,19 +26,16 @@ window.confirm = function (message?: string, title?: string) {
|
||||
};
|
||||
|
||||
const useEditMenuItems = function (x: number, y: number, items: ContextMenuItem[]) {
|
||||
return (
|
||||
items.length === 0 &&
|
||||
document.elementsFromPoint(x, y).some((element) => {
|
||||
return (
|
||||
element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA' || (element as HTMLElement).isContentEditable
|
||||
);
|
||||
})
|
||||
);
|
||||
return items.length === 0 && document.elementsFromPoint(x, y).some(element => {
|
||||
return element.nodeName === 'INPUT' ||
|
||||
element.nodeName === 'TEXTAREA' ||
|
||||
(element as HTMLElement).isContentEditable;
|
||||
});
|
||||
};
|
||||
|
||||
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) => {
|
||||
ipcRendererInternal.invoke<number>(IPC_MESSAGES.INSPECTOR_CONTEXT_MENU, items, isEditMenu).then(id => {
|
||||
if (typeof id === 'number') {
|
||||
webFrame.executeJavaScript(`window.DevToolsAPI.contextMenuItemSelected(${JSON.stringify(id)})`);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ const v8Util = process._linkedBinding('electron_common_v8_util');
|
||||
// ElectronApiServiceImpl will look for the "ipcNative" hidden object when
|
||||
// invoking the 'onMessage' callback.
|
||||
v8Util.setHiddenValue(globalThis, 'ipcNative', {
|
||||
onMessage(internal: boolean, channel: string, ports: MessagePort[], args: any[]) {
|
||||
onMessage (internal: boolean, channel: string, ports: MessagePort[], args: any[]) {
|
||||
const sender = internal ? ipcRendererInternal : ipcRenderer;
|
||||
sender.emit(channel, { sender, ports }, ...args);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ let ipc: NodeJS.IpcRendererImpl | undefined;
|
||||
/**
|
||||
* Get IPCRenderer implementation for the current process.
|
||||
*/
|
||||
export function getIPCRenderer() {
|
||||
export function getIPCRenderer () {
|
||||
if (ipc) return ipc;
|
||||
const ipcBinding = process._linkedBinding('electron_renderer_ipc');
|
||||
switch (process.type) {
|
||||
@@ -14,4 +14,4 @@ export function getIPCRenderer() {
|
||||
default:
|
||||
throw new Error(`Cannot create IPCRenderer for '${process.type}' process`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
|
||||
|
||||
type IPCHandler = (event: Electron.IpcRendererEvent, ...args: any[]) => any;
|
||||
type IPCHandler = (event: Electron.IpcRendererEvent, ...args: any[]) => any
|
||||
|
||||
export const handle = function <T extends IPCHandler>(channel: string, handler: T) {
|
||||
export const handle = function <T extends IPCHandler> (channel: string, handler: T) {
|
||||
ipcRendererInternal.on(channel, async (event, requestId, ...args) => {
|
||||
const replyChannel = `${channel}_RESPONSE_${requestId}`;
|
||||
try {
|
||||
@@ -13,7 +13,7 @@ export const handle = function <T extends IPCHandler>(channel: string, handler:
|
||||
});
|
||||
};
|
||||
|
||||
export function invokeSync<T>(command: string, ...args: any[]): T {
|
||||
export function invokeSync<T> (command: string, ...args: any[]): T {
|
||||
const [error, result] = ipcRendererInternal.sendSync(command, ...args);
|
||||
|
||||
if (error) {
|
||||
|
||||
@@ -6,21 +6,21 @@ const ipc = getIPCRenderer();
|
||||
const internal = true;
|
||||
|
||||
class IpcRendererInternal extends EventEmitter implements ElectronInternal.IpcRendererInternal {
|
||||
send(channel: string, ...args: any[]) {
|
||||
send (channel: string, ...args: any[]) {
|
||||
return ipc.send(internal, channel, args);
|
||||
}
|
||||
|
||||
sendSync(channel: string, ...args: any[]) {
|
||||
sendSync (channel: string, ...args: any[]) {
|
||||
return ipc.sendSync(internal, channel, args);
|
||||
}
|
||||
|
||||
async invoke<T>(channel: string, ...args: any[]) {
|
||||
async invoke<T> (channel: string, ...args: any[]) {
|
||||
const { error, result } = await ipc.invoke<T>(internal, channel, args);
|
||||
if (error) {
|
||||
throw new Error(`Error invoking remote method '${channel}': ${error}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const ipcRendererInternal = new IpcRendererInternal();
|
||||
|
||||
@@ -22,7 +22,8 @@ const shouldLogSecurityWarnings = function (): boolean {
|
||||
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
shouldLog = execPath.endsWith('MacOS/Electron') || execPath.includes('Electron.app/Contents/Frameworks/');
|
||||
shouldLog = execPath.endsWith('MacOS/Electron') ||
|
||||
execPath.includes('Electron.app/Contents/Frameworks/');
|
||||
break;
|
||||
case 'freebsd':
|
||||
case 'linux':
|
||||
@@ -35,11 +36,13 @@ const shouldLogSecurityWarnings = function (): boolean {
|
||||
shouldLog = false;
|
||||
}
|
||||
|
||||
if ((env && env.ELECTRON_DISABLE_SECURITY_WARNINGS) || (window && window.ELECTRON_DISABLE_SECURITY_WARNINGS)) {
|
||||
if ((env && env.ELECTRON_DISABLE_SECURITY_WARNINGS) ||
|
||||
(window && window.ELECTRON_DISABLE_SECURITY_WARNINGS)) {
|
||||
shouldLog = false;
|
||||
}
|
||||
|
||||
if ((env && env.ELECTRON_ENABLE_SECURITY_WARNINGS) || (window && window.ELECTRON_ENABLE_SECURITY_WARNINGS)) {
|
||||
if ((env && env.ELECTRON_ENABLE_SECURITY_WARNINGS) ||
|
||||
(window && window.ELECTRON_ENABLE_SECURITY_WARNINGS)) {
|
||||
shouldLog = true;
|
||||
}
|
||||
|
||||
@@ -94,8 +97,10 @@ const warnAboutInsecureResources = function () {
|
||||
return;
|
||||
}
|
||||
|
||||
const isLocal = (url: URL): boolean => ['localhost', '127.0.0.1', '[::1]', ''].includes(url.hostname);
|
||||
const isInsecure = (url: URL): boolean => ['http:', 'ftp:'].includes(url.protocol) && !isLocal(url);
|
||||
const isLocal = (url: URL): boolean =>
|
||||
['localhost', '127.0.0.1', '[::1]', ''].includes(url.hostname);
|
||||
const isInsecure = (url: URL): boolean =>
|
||||
['http:', 'ftp:'].includes(url.protocol) && !isLocal(url);
|
||||
|
||||
const resources = window.performance
|
||||
.getEntriesByType('resource')
|
||||
@@ -112,7 +117,8 @@ const warnAboutInsecureResources = function () {
|
||||
Consider loading the following resources over HTTPS or FTPS. \n${resources}
|
||||
\n${moreInformation}`;
|
||||
|
||||
console.warn('%cElectron Security Warning (Insecure Resources)', 'font-weight: bold;', warning);
|
||||
console.warn('%cElectron Security Warning (Insecure Resources)',
|
||||
'font-weight: bold;', warning);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -129,11 +135,8 @@ const warnAboutNodeWithRemoteContent = function (nodeIntegration: boolean) {
|
||||
and attempted to load remote content from '${window.location}'. This
|
||||
exposes users of this app to severe security risks.\n${moreInformation}`;
|
||||
|
||||
console.warn(
|
||||
'%cElectron Security Warning (Node.js Integration with Remote Content)',
|
||||
'font-weight: bold;',
|
||||
warning
|
||||
);
|
||||
console.warn('%cElectron Security Warning (Node.js Integration with Remote Content)',
|
||||
'font-weight: bold;', warning);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -154,7 +157,8 @@ const warnAboutDisabledWebSecurity = function (webPreferences?: Electron.WebPref
|
||||
const warning = `This renderer process has "webSecurity" disabled. This
|
||||
exposes users of this app to severe security risks.\n${moreInformation}`;
|
||||
|
||||
console.warn('%cElectron Security Warning (Disabled webSecurity)', 'font-weight: bold;', warning);
|
||||
console.warn('%cElectron Security Warning (Disabled webSecurity)',
|
||||
'font-weight: bold;', warning);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -170,7 +174,8 @@ const warnAboutInsecureCSP = function () {
|
||||
Policy set or a policy with "unsafe-eval" enabled. This exposes users of
|
||||
this app to unnecessary security risks.\n${moreInformation}`;
|
||||
|
||||
console.warn('%cElectron Security Warning (Insecure Content-Security-Policy)', 'font-weight: bold;', warning);
|
||||
console.warn('%cElectron Security Warning (Insecure Content-Security-Policy)',
|
||||
'font-weight: bold;', warning);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -185,7 +190,8 @@ const warnAboutInsecureContentAllowed = function (webPreferences?: Electron.WebP
|
||||
enabled. This exposes users of this app to severe security risks.\n
|
||||
${moreInformation}`;
|
||||
|
||||
console.warn('%cElectron Security Warning (allowRunningInsecureContent)', 'font-weight: bold;', warning);
|
||||
console.warn('%cElectron Security Warning (allowRunningInsecureContent)',
|
||||
'font-weight: bold;', warning);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -194,7 +200,7 @@ const warnAboutInsecureContentAllowed = function (webPreferences?: Electron.WebP
|
||||
* Logs a warning message about experimental features.
|
||||
*/
|
||||
const warnAboutExperimentalFeatures = function (webPreferences?: Electron.WebPreferences) {
|
||||
if (!webPreferences || !webPreferences.experimentalFeatures) {
|
||||
if (!webPreferences || (!webPreferences.experimentalFeatures)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -202,7 +208,8 @@ const warnAboutExperimentalFeatures = function (webPreferences?: Electron.WebPre
|
||||
This exposes users of this app to some security risk. If you do not need
|
||||
this feature, you should disable it.\n${moreInformation}`;
|
||||
|
||||
console.warn('%cElectron Security Warning (experimentalFeatures)', 'font-weight: bold;', warning);
|
||||
console.warn('%cElectron Security Warning (experimentalFeatures)',
|
||||
'font-weight: bold;', warning);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -211,11 +218,9 @@ const warnAboutExperimentalFeatures = function (webPreferences?: Electron.WebPre
|
||||
* Logs a warning message about enableBlinkFeatures
|
||||
*/
|
||||
const warnAboutEnableBlinkFeatures = function (webPreferences?: Electron.WebPreferences) {
|
||||
if (
|
||||
!webPreferences ||
|
||||
if (!webPreferences ||
|
||||
!Object.hasOwn(webPreferences, 'enableBlinkFeatures') ||
|
||||
(webPreferences.enableBlinkFeatures != null && webPreferences.enableBlinkFeatures.length === 0)
|
||||
) {
|
||||
(webPreferences.enableBlinkFeatures != null && webPreferences.enableBlinkFeatures.length === 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -223,7 +228,8 @@ const warnAboutEnableBlinkFeatures = function (webPreferences?: Electron.WebPref
|
||||
enabled. This exposes users of this app to some security risk. If you do not
|
||||
need this feature, you should disable it.\n${moreInformation}`;
|
||||
|
||||
console.warn('%cElectron Security Warning (enableBlinkFeatures)', 'font-weight: bold;', warning);
|
||||
console.warn('%cElectron Security Warning (enableBlinkFeatures)',
|
||||
'font-weight: bold;', warning);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -244,7 +250,8 @@ const warnAboutAllowedPopups = function () {
|
||||
BrowserWindows. If you do not need this feature, you should disable it.\n
|
||||
${moreInformation}`;
|
||||
|
||||
console.warn('%cElectron Security Warning (allowpopups)', 'font-weight: bold;', warning);
|
||||
console.warn('%cElectron Security Warning (allowpopups)',
|
||||
'font-weight: bold;', warning);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -254,7 +261,9 @@ const warnAboutAllowedPopups = function () {
|
||||
// #13 Disable or limit creation of new windows
|
||||
// #14 Do not use `openExternal` with untrusted content
|
||||
|
||||
const logSecurityWarnings = function (webPreferences: Electron.WebPreferences | undefined, nodeIntegration: boolean) {
|
||||
const logSecurityWarnings = function (
|
||||
webPreferences: Electron.WebPreferences | undefined, nodeIntegration: boolean
|
||||
) {
|
||||
warnAboutNodeWithRemoteContent(nodeIntegration);
|
||||
warnAboutDisabledWebSecurity(webPreferences);
|
||||
warnAboutInsecureResources();
|
||||
@@ -273,7 +282,7 @@ const getWebPreferences = async function () {
|
||||
}
|
||||
};
|
||||
|
||||
export function securityWarnings(nodeIntegration: boolean) {
|
||||
export function securityWarnings (nodeIntegration: boolean) {
|
||||
const loadHandler = async function () {
|
||||
if (shouldLogSecurityWarnings()) {
|
||||
const webPreferences = await getWebPreferences();
|
||||
|
||||
@@ -5,18 +5,18 @@ import { webFrame, WebFrame } from 'electron/renderer';
|
||||
|
||||
// All keys of WebFrame that extend Function
|
||||
type WebFrameMethod = {
|
||||
[K in keyof WebFrame]: WebFrame[K] extends Function ? K : never;
|
||||
};
|
||||
[K in keyof WebFrame]:
|
||||
WebFrame[K] extends Function ? K : never
|
||||
}
|
||||
|
||||
export const webFrameInit = () => {
|
||||
// Call webFrame method
|
||||
ipcRendererUtils.handle(
|
||||
IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD,
|
||||
(event, method: keyof WebFrameMethod, ...args: any[]) => {
|
||||
// The TypeScript compiler cannot handle the sheer number of
|
||||
// call signatures here and simply gives up. Incorrect invocations
|
||||
// will be caught by "keyof WebFrameMethod" though.
|
||||
return (webFrame[method] as any)(...args);
|
||||
}
|
||||
);
|
||||
ipcRendererUtils.handle(IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD, (
|
||||
event, method: keyof WebFrameMethod, ...args: any[]
|
||||
) => {
|
||||
// The TypeScript compiler cannot handle the sheer number of
|
||||
// call signatures here and simply gives up. Incorrect invocations
|
||||
// will be caught by "keyof WebFrameMethod" though.
|
||||
return (webFrame[method] as any)(...args);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -5,61 +5,48 @@ import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-inte
|
||||
const { mainFrame: webFrame } = process._linkedBinding('electron_renderer_web_frame');
|
||||
|
||||
export interface GuestViewDelegate {
|
||||
dispatchEvent(eventName: string, props: Record<string, any>): void;
|
||||
dispatchEvent (eventName: string, props: Record<string, any>): void;
|
||||
}
|
||||
|
||||
export function registerEvents(viewInstanceId: number, delegate: GuestViewDelegate) {
|
||||
ipcRendererInternal.on(
|
||||
`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT}-${viewInstanceId}`,
|
||||
function (event, eventName, props) {
|
||||
delegate.dispatchEvent(eventName, props);
|
||||
}
|
||||
);
|
||||
export function registerEvents (viewInstanceId: number, delegate: GuestViewDelegate) {
|
||||
ipcRendererInternal.on(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT}-${viewInstanceId}`, function (event, eventName, props) {
|
||||
delegate.dispatchEvent(eventName, props);
|
||||
});
|
||||
}
|
||||
|
||||
export function deregisterEvents(viewInstanceId: number) {
|
||||
export function deregisterEvents (viewInstanceId: number) {
|
||||
ipcRendererInternal.removeAllListeners(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT}-${viewInstanceId}`);
|
||||
}
|
||||
|
||||
export function createGuest(
|
||||
iframe: HTMLIFrameElement,
|
||||
elementInstanceId: number,
|
||||
params: Record<string, any>
|
||||
): Promise<number> {
|
||||
export function createGuest (iframe: HTMLIFrameElement, elementInstanceId: number, params: Record<string, any>): Promise<number> {
|
||||
if (!(iframe instanceof HTMLIFrameElement)) {
|
||||
throw new TypeError('Invalid embedder frame');
|
||||
}
|
||||
|
||||
const embedderFrame = webFrame._findFrameByWindow(iframe.contentWindow!);
|
||||
if (!embedderFrame) {
|
||||
// this error should not happen.
|
||||
if (!embedderFrame) { // this error should not happen.
|
||||
throw new Error('Invalid embedder frame');
|
||||
}
|
||||
|
||||
return ipcRendererInternal.invoke(
|
||||
IPC_MESSAGES.GUEST_VIEW_MANAGER_CREATE_AND_ATTACH_GUEST,
|
||||
embedderFrame.frameToken,
|
||||
elementInstanceId,
|
||||
params
|
||||
);
|
||||
return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_CREATE_AND_ATTACH_GUEST, embedderFrame.frameToken, elementInstanceId, params);
|
||||
}
|
||||
|
||||
export function detachGuest(guestInstanceId: number) {
|
||||
export function detachGuest (guestInstanceId: number) {
|
||||
return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_DETACH_GUEST, guestInstanceId);
|
||||
}
|
||||
|
||||
export function invoke(guestInstanceId: number, method: string, args: any[]) {
|
||||
export function invoke (guestInstanceId: number, method: string, args: any[]) {
|
||||
return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL, guestInstanceId, method, args);
|
||||
}
|
||||
|
||||
export function invokeSync(guestInstanceId: number, method: string, args: any[]) {
|
||||
export function invokeSync (guestInstanceId: number, method: string, args: any[]) {
|
||||
return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL, guestInstanceId, method, args);
|
||||
}
|
||||
|
||||
export function propertyGet(guestInstanceId: number, name: string) {
|
||||
export function propertyGet (guestInstanceId: number, name: string) {
|
||||
return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_PROPERTY_GET, guestInstanceId, name);
|
||||
}
|
||||
|
||||
export function propertySet(guestInstanceId: number, name: string, value: any) {
|
||||
export function propertySet (guestInstanceId: number, name: string, value: any) {
|
||||
return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_PROPERTY_SET, guestInstanceId, name, value);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ const resolveURL = function (url?: string | null) {
|
||||
};
|
||||
|
||||
interface MutationHandler {
|
||||
handleMutation(_oldValue: any, _newValue: any): any;
|
||||
handleMutation (_oldValue: any, _newValue: any): any;
|
||||
}
|
||||
|
||||
// Attribute objects.
|
||||
@@ -15,10 +15,7 @@ export class WebViewAttribute implements MutationHandler {
|
||||
public value: any;
|
||||
public ignoreMutation = false;
|
||||
|
||||
constructor(
|
||||
public name: string,
|
||||
public webViewImpl: WebViewImpl
|
||||
) {
|
||||
constructor (public name: string, public webViewImpl: WebViewImpl) {
|
||||
this.name = name;
|
||||
this.value = (webViewImpl.webviewNode as Record<string, any>)[name] || '';
|
||||
this.webViewImpl = webViewImpl;
|
||||
@@ -26,24 +23,24 @@ export class WebViewAttribute implements MutationHandler {
|
||||
}
|
||||
|
||||
// Retrieves and returns the attribute's value.
|
||||
public getValue() {
|
||||
public getValue () {
|
||||
return this.webViewImpl.webviewNode.getAttribute(this.name) || this.value;
|
||||
}
|
||||
|
||||
// Sets the attribute's value.
|
||||
public setValue(value: any) {
|
||||
public setValue (value: any) {
|
||||
this.webViewImpl.webviewNode.setAttribute(this.name, value || '');
|
||||
}
|
||||
|
||||
// Changes the attribute's value without triggering its mutation handler.
|
||||
public setValueIgnoreMutation(value: any) {
|
||||
public setValueIgnoreMutation (value: any) {
|
||||
this.ignoreMutation = true;
|
||||
this.setValue(value);
|
||||
this.ignoreMutation = false;
|
||||
}
|
||||
|
||||
// Defines this attribute as a property on the webview node.
|
||||
public defineProperty() {
|
||||
public defineProperty () {
|
||||
return Object.defineProperty(this.webViewImpl.webviewNode, this.name, {
|
||||
get: () => {
|
||||
return this.getValue();
|
||||
@@ -61,11 +58,11 @@ export class WebViewAttribute implements MutationHandler {
|
||||
|
||||
// An attribute that is treated as a Boolean.
|
||||
class BooleanAttribute extends WebViewAttribute {
|
||||
getValue() {
|
||||
getValue () {
|
||||
return this.webViewImpl.webviewNode.hasAttribute(this.name);
|
||||
}
|
||||
|
||||
setValue(value: boolean) {
|
||||
setValue (value: boolean) {
|
||||
if (value) {
|
||||
this.webViewImpl.webviewNode.setAttribute(this.name, '');
|
||||
} else {
|
||||
@@ -78,7 +75,7 @@ class BooleanAttribute extends WebViewAttribute {
|
||||
export class PartitionAttribute extends WebViewAttribute {
|
||||
public validPartitionId = true;
|
||||
|
||||
constructor(public webViewImpl: WebViewImpl) {
|
||||
constructor (public webViewImpl: WebViewImpl) {
|
||||
super(WEB_VIEW_ATTRIBUTES.PARTITION, webViewImpl);
|
||||
}
|
||||
|
||||
@@ -102,12 +99,12 @@ export class PartitionAttribute extends WebViewAttribute {
|
||||
export class SrcAttribute extends WebViewAttribute {
|
||||
public observer!: MutationObserver;
|
||||
|
||||
constructor(public webViewImpl: WebViewImpl) {
|
||||
constructor (public webViewImpl: WebViewImpl) {
|
||||
super(WEB_VIEW_ATTRIBUTES.SRC, webViewImpl);
|
||||
this.setupMutationObserver();
|
||||
}
|
||||
|
||||
public getValue() {
|
||||
public getValue () {
|
||||
if (this.webViewImpl.webviewNode.hasAttribute(this.name)) {
|
||||
return resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name));
|
||||
} else {
|
||||
@@ -115,7 +112,7 @@ export class SrcAttribute extends WebViewAttribute {
|
||||
}
|
||||
}
|
||||
|
||||
public setValueIgnoreMutation(value: any) {
|
||||
public setValueIgnoreMutation (value: any) {
|
||||
super.setValueIgnoreMutation(value);
|
||||
|
||||
// takeRecords() is needed to clear queued up src mutations. Without it, it
|
||||
@@ -143,7 +140,7 @@ export class SrcAttribute extends WebViewAttribute {
|
||||
// attribute without any changes to its value. This is useful in the case
|
||||
// where the webview guest has crashed and navigating to the same address
|
||||
// spawns off a new process.
|
||||
public setupMutationObserver() {
|
||||
public setupMutationObserver () {
|
||||
this.observer = new MutationObserver((mutations) => {
|
||||
for (const mutation of mutations) {
|
||||
const { oldValue } = mutation;
|
||||
@@ -164,12 +161,8 @@ export class SrcAttribute extends WebViewAttribute {
|
||||
this.observer.observe(this.webViewImpl.webviewNode, params);
|
||||
}
|
||||
|
||||
public parse() {
|
||||
if (
|
||||
!this.webViewImpl.elementAttached ||
|
||||
!(this.webViewImpl.attributes.get(WEB_VIEW_ATTRIBUTES.PARTITION) as PartitionAttribute).validPartitionId ||
|
||||
!this.getValue()
|
||||
) {
|
||||
public parse () {
|
||||
if (!this.webViewImpl.elementAttached || !(this.webViewImpl.attributes.get(WEB_VIEW_ATTRIBUTES.PARTITION) as PartitionAttribute).validPartitionId || !this.getValue()) {
|
||||
return;
|
||||
}
|
||||
if (this.webViewImpl.guestInstanceId == null) {
|
||||
@@ -193,33 +186,34 @@ export class SrcAttribute extends WebViewAttribute {
|
||||
opts.userAgent = useragent;
|
||||
}
|
||||
|
||||
(this.webViewImpl.webviewNode as Electron.WebviewTag).loadURL(this.getValue(), opts).catch((err) => {
|
||||
console.error('Unexpected error while loading URL', err);
|
||||
});
|
||||
(this.webViewImpl.webviewNode as Electron.WebviewTag).loadURL(this.getValue(), opts)
|
||||
.catch(err => {
|
||||
console.error('Unexpected error while loading URL', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Attribute specifies HTTP referrer.
|
||||
class HttpReferrerAttribute extends WebViewAttribute {
|
||||
constructor(webViewImpl: WebViewImpl) {
|
||||
constructor (webViewImpl: WebViewImpl) {
|
||||
super(WEB_VIEW_ATTRIBUTES.HTTPREFERRER, webViewImpl);
|
||||
}
|
||||
}
|
||||
|
||||
// Attribute specifies user agent
|
||||
class UserAgentAttribute extends WebViewAttribute {
|
||||
constructor(webViewImpl: WebViewImpl) {
|
||||
constructor (webViewImpl: WebViewImpl) {
|
||||
super(WEB_VIEW_ATTRIBUTES.USERAGENT, webViewImpl);
|
||||
}
|
||||
}
|
||||
|
||||
// Attribute that set preload script.
|
||||
class PreloadAttribute extends WebViewAttribute {
|
||||
constructor(webViewImpl: WebViewImpl) {
|
||||
constructor (webViewImpl: WebViewImpl) {
|
||||
super(WEB_VIEW_ATTRIBUTES.PRELOAD, webViewImpl);
|
||||
}
|
||||
|
||||
public getValue() {
|
||||
public getValue () {
|
||||
if (!this.webViewImpl.webviewNode.hasAttribute(this.name)) {
|
||||
return this.value;
|
||||
}
|
||||
@@ -238,37 +232,34 @@ class PreloadAttribute extends WebViewAttribute {
|
||||
|
||||
// Attribute that specifies the blink features to be enabled.
|
||||
class BlinkFeaturesAttribute extends WebViewAttribute {
|
||||
constructor(webViewImpl: WebViewImpl) {
|
||||
constructor (webViewImpl: WebViewImpl) {
|
||||
super(WEB_VIEW_ATTRIBUTES.BLINKFEATURES, webViewImpl);
|
||||
}
|
||||
}
|
||||
|
||||
// Attribute that specifies the blink features to be disabled.
|
||||
class DisableBlinkFeaturesAttribute extends WebViewAttribute {
|
||||
constructor(webViewImpl: WebViewImpl) {
|
||||
constructor (webViewImpl: WebViewImpl) {
|
||||
super(WEB_VIEW_ATTRIBUTES.DISABLEBLINKFEATURES, webViewImpl);
|
||||
}
|
||||
}
|
||||
|
||||
// Attribute that specifies the web preferences to be enabled.
|
||||
class WebPreferencesAttribute extends WebViewAttribute {
|
||||
constructor(webViewImpl: WebViewImpl) {
|
||||
constructor (webViewImpl: WebViewImpl) {
|
||||
super(WEB_VIEW_ATTRIBUTES.WEBPREFERENCES, webViewImpl);
|
||||
}
|
||||
}
|
||||
|
||||
// Sets up all of the webview attributes.
|
||||
export function setupWebViewAttributes(self: WebViewImpl) {
|
||||
export function setupWebViewAttributes (self: WebViewImpl) {
|
||||
return new Map<string, WebViewAttribute>([
|
||||
[WEB_VIEW_ATTRIBUTES.PARTITION, new PartitionAttribute(self)],
|
||||
[WEB_VIEW_ATTRIBUTES.SRC, new SrcAttribute(self)],
|
||||
[WEB_VIEW_ATTRIBUTES.HTTPREFERRER, new HttpReferrerAttribute(self)],
|
||||
[WEB_VIEW_ATTRIBUTES.USERAGENT, new UserAgentAttribute(self)],
|
||||
[WEB_VIEW_ATTRIBUTES.NODEINTEGRATION, new BooleanAttribute(WEB_VIEW_ATTRIBUTES.NODEINTEGRATION, self)],
|
||||
[
|
||||
WEB_VIEW_ATTRIBUTES.NODEINTEGRATIONINSUBFRAMES,
|
||||
new BooleanAttribute(WEB_VIEW_ATTRIBUTES.NODEINTEGRATIONINSUBFRAMES, self)
|
||||
],
|
||||
[WEB_VIEW_ATTRIBUTES.NODEINTEGRATIONINSUBFRAMES, new BooleanAttribute(WEB_VIEW_ATTRIBUTES.NODEINTEGRATIONINSUBFRAMES, self)],
|
||||
[WEB_VIEW_ATTRIBUTES.PLUGINS, new BooleanAttribute(WEB_VIEW_ATTRIBUTES.PLUGINS, self)],
|
||||
[WEB_VIEW_ATTRIBUTES.DISABLEWEBSECURITY, new BooleanAttribute(WEB_VIEW_ATTRIBUTES.DISABLEWEBSECURITY, self)],
|
||||
[WEB_VIEW_ATTRIBUTES.ALLOWPOPUPS, new BooleanAttribute(WEB_VIEW_ATTRIBUTES.ALLOWPOPUPS, self)],
|
||||
|
||||
@@ -12,7 +12,7 @@ export const enum WEB_VIEW_ATTRIBUTES {
|
||||
USERAGENT = 'useragent',
|
||||
BLINKFEATURES = 'blinkfeatures',
|
||||
DISABLEBLINKFEATURES = 'disableblinkfeatures',
|
||||
WEBPREFERENCES = 'webpreferences'
|
||||
WEBPREFERENCES = 'webpreferences',
|
||||
}
|
||||
|
||||
export const enum WEB_VIEW_ERROR_MESSAGES {
|
||||
|
||||
@@ -17,7 +17,7 @@ const internals = new WeakMap<HTMLElement, WebViewImpl>();
|
||||
// Return a WebViewElement class that is defined in this context.
|
||||
const defineWebViewElement = (hooks: WebViewImplHooks) => {
|
||||
return class WebViewElement extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
static get observedAttributes () {
|
||||
return [
|
||||
WEB_VIEW_ATTRIBUTES.PARTITION,
|
||||
WEB_VIEW_ATTRIBUTES.SRC,
|
||||
@@ -35,12 +35,12 @@ const defineWebViewElement = (hooks: WebViewImplHooks) => {
|
||||
];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
constructor () {
|
||||
super();
|
||||
internals.set(this, new WebViewImpl(this, hooks));
|
||||
}
|
||||
|
||||
getWebContentsId() {
|
||||
getWebContentsId () {
|
||||
const internal = internals.get(this);
|
||||
if (!internal || !internal.guestInstanceId) {
|
||||
throw new Error(WEB_VIEW_ERROR_MESSAGES.NOT_ATTACHED);
|
||||
@@ -48,7 +48,7 @@ const defineWebViewElement = (hooks: WebViewImplHooks) => {
|
||||
return internal.guestInstanceId;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
connectedCallback () {
|
||||
const internal = internals.get(this);
|
||||
if (!internal) {
|
||||
return;
|
||||
@@ -62,14 +62,14 @@ const defineWebViewElement = (hooks: WebViewImplHooks) => {
|
||||
}
|
||||
}
|
||||
|
||||
attributeChangedCallback(name: string, oldValue: any, newValue: any) {
|
||||
attributeChangedCallback (name: string, oldValue: any, newValue: any) {
|
||||
const internal = internals.get(this);
|
||||
if (internal) {
|
||||
internal.handleWebviewAttributeMutation(name, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
disconnectedCallback () {
|
||||
const internal = internals.get(this);
|
||||
if (!internal) {
|
||||
return;
|
||||
|
||||
@@ -32,10 +32,7 @@ export class WebViewImpl {
|
||||
|
||||
public attributes: Map<string, WebViewAttribute>;
|
||||
|
||||
constructor(
|
||||
public webviewNode: HTMLElement,
|
||||
private hooks: WebViewImplHooks
|
||||
) {
|
||||
constructor (public webviewNode: HTMLElement, private hooks: WebViewImplHooks) {
|
||||
// Create internal iframe element.
|
||||
this.internalElement = this.createInternalElement();
|
||||
const shadowRoot = this.webviewNode.attachShadow({ mode: 'open' });
|
||||
@@ -55,7 +52,7 @@ export class WebViewImpl {
|
||||
});
|
||||
}
|
||||
|
||||
createInternalElement() {
|
||||
createInternalElement () {
|
||||
const iframeElement = document.createElement('iframe');
|
||||
iframeElement.style.flex = '1 1 auto';
|
||||
iframeElement.style.width = '100%';
|
||||
@@ -66,7 +63,7 @@ export class WebViewImpl {
|
||||
}
|
||||
|
||||
// Resets some state upon reattaching <webview> element to the DOM.
|
||||
reset() {
|
||||
reset () {
|
||||
// If guestInstanceId is defined then the <webview> has navigated and has
|
||||
// already picked up a partition ID. Thus, we need to reset the initialization
|
||||
// state. However, it may be the case that beforeFirstNavigation is false BUT
|
||||
@@ -96,7 +93,7 @@ export class WebViewImpl {
|
||||
// a BrowserPlugin property will update the corresponding BrowserPlugin
|
||||
// attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more
|
||||
// details.
|
||||
handleWebviewAttributeMutation(attributeName: string, oldValue: any, newValue: any) {
|
||||
handleWebviewAttributeMutation (attributeName: string, oldValue: any, newValue: any) {
|
||||
if (!this.attributes.has(attributeName) || this.attributes.get(attributeName)!.ignoreMutation) {
|
||||
return;
|
||||
}
|
||||
@@ -105,16 +102,15 @@ export class WebViewImpl {
|
||||
this.attributes.get(attributeName)!.handleMutation(oldValue, newValue);
|
||||
}
|
||||
|
||||
createGuest() {
|
||||
createGuest () {
|
||||
this.internalInstanceId = getNextId();
|
||||
this.hooks.guestViewInternal
|
||||
.createGuest(this.internalElement, this.internalInstanceId, this.buildParams())
|
||||
.then((guestInstanceId) => {
|
||||
this.hooks.guestViewInternal.createGuest(this.internalElement, this.internalInstanceId, this.buildParams())
|
||||
.then(guestInstanceId => {
|
||||
this.attachGuestInstance(guestInstanceId);
|
||||
});
|
||||
}
|
||||
|
||||
dispatchEvent(eventName: string, props: Record<string, any> = {}) {
|
||||
dispatchEvent (eventName: string, props: Record<string, any> = {}) {
|
||||
const event = new Event(eventName);
|
||||
Object.assign(event, props);
|
||||
this.webviewNode.dispatchEvent(event);
|
||||
@@ -128,7 +124,7 @@ export class WebViewImpl {
|
||||
|
||||
// Adds an 'on<event>' property on the webview, which can be used to set/unset
|
||||
// an event handler.
|
||||
setupEventProperty(eventName: string) {
|
||||
setupEventProperty (eventName: string) {
|
||||
const propertyName = `on${eventName.toLowerCase()}`;
|
||||
return Object.defineProperty(this.webviewNode, propertyName, {
|
||||
get: () => {
|
||||
@@ -148,10 +144,10 @@ export class WebViewImpl {
|
||||
}
|
||||
|
||||
// Updates state upon loadcommit.
|
||||
onLoadCommit(props: Record<string, any>) {
|
||||
onLoadCommit (props: Record<string, any>) {
|
||||
const oldValue = this.webviewNode.getAttribute(WEB_VIEW_ATTRIBUTES.SRC);
|
||||
const newValue = props.url;
|
||||
if (props.isMainFrame && oldValue !== newValue) {
|
||||
if (props.isMainFrame && (oldValue !== newValue)) {
|
||||
// Touching the src attribute triggers a navigation. To avoid
|
||||
// triggering a page reload on every guest-initiated navigation,
|
||||
// we do not handle this mutation.
|
||||
@@ -160,7 +156,7 @@ export class WebViewImpl {
|
||||
}
|
||||
|
||||
// Emits focus/blur events.
|
||||
onFocusChange() {
|
||||
onFocusChange () {
|
||||
const hasFocus = this.webviewNode.ownerDocument.activeElement === this.webviewNode;
|
||||
if (hasFocus !== this.hasFocus) {
|
||||
this.hasFocus = hasFocus;
|
||||
@@ -168,11 +164,11 @@ export class WebViewImpl {
|
||||
}
|
||||
}
|
||||
|
||||
onAttach(storagePartitionId: number) {
|
||||
onAttach (storagePartitionId: number) {
|
||||
return this.attributes.get(WEB_VIEW_ATTRIBUTES.PARTITION)!.setValue(storagePartitionId);
|
||||
}
|
||||
|
||||
buildParams() {
|
||||
buildParams () {
|
||||
const params: Record<string, any> = {
|
||||
instanceId: this.viewInstanceId
|
||||
};
|
||||
@@ -184,7 +180,7 @@ export class WebViewImpl {
|
||||
return params;
|
||||
}
|
||||
|
||||
attachGuestInstance(guestInstanceId: number) {
|
||||
attachGuestInstance (guestInstanceId: number) {
|
||||
if (guestInstanceId === -1) {
|
||||
this.dispatchEvent('destroyed');
|
||||
return;
|
||||
@@ -209,19 +205,13 @@ export const setupMethods = (WebViewElement: typeof ElectronInternal.WebViewElem
|
||||
|
||||
// Forward proto.foo* method calls to WebViewImpl.foo*.
|
||||
for (const method of syncMethods) {
|
||||
(WebViewElement.prototype as Record<string, any>)[method] = function (
|
||||
this: ElectronInternal.WebViewElement,
|
||||
...args: Array<any>
|
||||
) {
|
||||
(WebViewElement.prototype as Record<string, any>)[method] = function (this: ElectronInternal.WebViewElement, ...args: Array<any>) {
|
||||
return hooks.guestViewInternal.invokeSync(this.getWebContentsId(), method, args);
|
||||
};
|
||||
}
|
||||
|
||||
for (const method of asyncMethods) {
|
||||
(WebViewElement.prototype as Record<string, any>)[method] = function (
|
||||
this: ElectronInternal.WebViewElement,
|
||||
...args: Array<any>
|
||||
) {
|
||||
(WebViewElement.prototype as Record<string, any>)[method] = function (this: ElectronInternal.WebViewElement, ...args: Array<any>) {
|
||||
return hooks.guestViewInternal.invoke(this.getWebContentsId(), method, args);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import type * as webViewElementModule from '@electron/internal/renderer/web-view
|
||||
const v8Util = process._linkedBinding('electron_common_v8_util');
|
||||
const { mainFrame: webFrame } = process._linkedBinding('electron_renderer_web_frame');
|
||||
|
||||
function handleFocusBlur() {
|
||||
function handleFocusBlur () {
|
||||
// Note that while Chromium content APIs have observer for focus/blur, they
|
||||
// unfortunately do not work for webview.
|
||||
|
||||
@@ -19,20 +19,18 @@ function handleFocusBlur() {
|
||||
});
|
||||
}
|
||||
|
||||
export function webViewInit(webviewTag: boolean, isWebView: boolean) {
|
||||
export function webViewInit (webviewTag: boolean, isWebView: boolean) {
|
||||
// Don't allow recursive `<webview>`.
|
||||
if (webviewTag && !isWebView) {
|
||||
const guestViewInternal =
|
||||
require('@electron/internal/renderer/web-view/guest-view-internal') as typeof guestViewInternalModule;
|
||||
const guestViewInternal = require('@electron/internal/renderer/web-view/guest-view-internal') as typeof guestViewInternalModule;
|
||||
if (process.contextIsolated) {
|
||||
v8Util.setHiddenValue(window, 'guestViewInternal', guestViewInternal);
|
||||
} else {
|
||||
const { setupWebView } =
|
||||
require('@electron/internal/renderer/web-view/web-view-element') as typeof webViewElementModule;
|
||||
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element') as typeof webViewElementModule;
|
||||
setupWebView({
|
||||
guestViewInternal,
|
||||
allowGuestViewElementDefinition: webFrame.allowGuestViewElementDefinition,
|
||||
setIsWebView: (iframe) => v8Util.setHiddenValue(iframe, 'isWebView', true)
|
||||
setIsWebView: iframe => v8Util.setHiddenValue(iframe, 'isWebView', true)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,34 +30,24 @@ export const windowSetup = (isWebView: boolean, isHiddenPage: boolean) => {
|
||||
let cachedVisibilityState = isHiddenPage ? 'hidden' : 'visible';
|
||||
|
||||
// Subscribe to visibilityState changes.
|
||||
ipcRendererInternal.on(
|
||||
IPC_MESSAGES.GUEST_INSTANCE_VISIBILITY_CHANGE,
|
||||
function (_event, visibilityState: DocumentVisibilityState) {
|
||||
if (cachedVisibilityState !== visibilityState) {
|
||||
cachedVisibilityState = visibilityState;
|
||||
document.dispatchEvent(new Event('visibilitychange'));
|
||||
}
|
||||
ipcRendererInternal.on(IPC_MESSAGES.GUEST_INSTANCE_VISIBILITY_CHANGE, function (_event, visibilityState: DocumentVisibilityState) {
|
||||
if (cachedVisibilityState !== visibilityState) {
|
||||
cachedVisibilityState = visibilityState;
|
||||
document.dispatchEvent(new Event('visibilitychange'));
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Make document.hidden and document.visibilityState return the correct value.
|
||||
const getDocumentHidden = () => cachedVisibilityState !== 'visible';
|
||||
Object.defineProperty(document, 'hidden', {
|
||||
get: getDocumentHidden
|
||||
});
|
||||
if (contextIsolationEnabled) {
|
||||
internalContextBridge.overrideGlobalPropertyFromIsolatedWorld(['document', 'hidden'], getDocumentHidden);
|
||||
}
|
||||
if (contextIsolationEnabled) internalContextBridge.overrideGlobalPropertyFromIsolatedWorld(['document', 'hidden'], getDocumentHidden);
|
||||
|
||||
const getDocumentVisibilityState = () => cachedVisibilityState;
|
||||
Object.defineProperty(document, 'visibilityState', {
|
||||
get: getDocumentVisibilityState
|
||||
});
|
||||
if (contextIsolationEnabled) {
|
||||
internalContextBridge.overrideGlobalPropertyFromIsolatedWorld(
|
||||
['document', 'visibilityState'],
|
||||
getDocumentVisibilityState
|
||||
);
|
||||
}
|
||||
if (contextIsolationEnabled) internalContextBridge.overrideGlobalPropertyFromIsolatedWorld(['document', 'visibilityState'], getDocumentVisibilityState);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
import '@electron/internal/sandboxed_renderer/pre-init';
|
||||
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
|
||||
import type * as ipcRendererUtilsModule from '@electron/internal/renderer/ipc-renderer-internal-utils';
|
||||
import {
|
||||
createPreloadProcessObject,
|
||||
executeSandboxedPreloadScripts
|
||||
} from '@electron/internal/sandboxed_renderer/preload';
|
||||
import { createPreloadProcessObject, executeSandboxedPreloadScripts } from '@electron/internal/sandboxed_renderer/preload';
|
||||
|
||||
import * as events from 'events';
|
||||
import { setImmediate, clearImmediate } from 'timers';
|
||||
|
||||
declare const binding: {
|
||||
process: NodeJS.Process;
|
||||
createPreloadScript: (src: string) => Function;
|
||||
createPreloadScript: (src: string) => Function
|
||||
};
|
||||
|
||||
const ipcRendererUtils =
|
||||
require('@electron/internal/renderer/ipc-renderer-internal-utils') as typeof ipcRendererUtilsModule;
|
||||
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils') as typeof ipcRendererUtilsModule;
|
||||
|
||||
const { preloadScripts, process: processProps } = ipcRendererUtils.invokeSync<{
|
||||
const {
|
||||
preloadScripts,
|
||||
process: processProps
|
||||
} = ipcRendererUtils.invokeSync<{
|
||||
preloadScripts: ElectronInternal.PreloadScript[];
|
||||
process: NodeJS.Process;
|
||||
}>(IPC_MESSAGES.BROWSER_SANDBOX_LOAD);
|
||||
@@ -56,20 +55,17 @@ Object.assign(process, processProps);
|
||||
// Common renderer initialization
|
||||
require('@electron/internal/renderer/common-init');
|
||||
|
||||
executeSandboxedPreloadScripts(
|
||||
{
|
||||
loadedModules,
|
||||
loadableModules,
|
||||
process: preloadProcess,
|
||||
createPreloadScript: binding.createPreloadScript,
|
||||
exposeGlobals: {
|
||||
Buffer,
|
||||
// FIXME(samuelmaddock): workaround webpack bug replacing this with just
|
||||
// `__webpack_require__.g,` which causes script error
|
||||
global: globalThis,
|
||||
setImmediate,
|
||||
clearImmediate
|
||||
}
|
||||
},
|
||||
preloadScripts
|
||||
);
|
||||
executeSandboxedPreloadScripts({
|
||||
loadedModules,
|
||||
loadableModules,
|
||||
process: preloadProcess,
|
||||
createPreloadScript: binding.createPreloadScript,
|
||||
exposeGlobals: {
|
||||
Buffer,
|
||||
// FIXME(samuelmaddock): workaround webpack bug replacing this with just
|
||||
// `__webpack_require__.g,` which causes script error
|
||||
global: globalThis,
|
||||
setImmediate,
|
||||
clearImmediate
|
||||
}
|
||||
}, preloadScripts);
|
||||
|
||||
@@ -10,13 +10,13 @@ interface PreloadContext {
|
||||
/** Process object to pass into preloads. */
|
||||
process: NodeJS.Process;
|
||||
|
||||
createPreloadScript: (src: string) => Function;
|
||||
createPreloadScript: (src: string) => Function
|
||||
|
||||
/** Globals to be exposed to preload context. */
|
||||
exposeGlobals: any;
|
||||
}
|
||||
|
||||
export function createPreloadProcessObject(): NodeJS.Process {
|
||||
export function createPreloadProcessObject (): NodeJS.Process {
|
||||
const preloadProcess: NodeJS.Process = new EventEmitter() as any;
|
||||
|
||||
preloadProcess.getProcessMemoryInfo = () => {
|
||||
@@ -24,10 +24,10 @@ export function createPreloadProcessObject(): NodeJS.Process {
|
||||
};
|
||||
|
||||
Object.defineProperty(preloadProcess, 'noDeprecation', {
|
||||
get() {
|
||||
get () {
|
||||
return process.noDeprecation;
|
||||
},
|
||||
set(value) {
|
||||
set (value) {
|
||||
process.noDeprecation = value;
|
||||
}
|
||||
});
|
||||
@@ -44,7 +44,7 @@ export function createPreloadProcessObject(): NodeJS.Process {
|
||||
}
|
||||
|
||||
// This is the `require` function that will be visible to the preload script
|
||||
function preloadRequire(context: PreloadContext, module: string) {
|
||||
function preloadRequire (context: PreloadContext, module: string) {
|
||||
if (context.loadedModules.has(module)) {
|
||||
return context.loadedModules.get(module);
|
||||
}
|
||||
@@ -63,7 +63,7 @@ function preloadRequire(context: PreloadContext, module: string) {
|
||||
// - `process`: The `preloadProcess` object
|
||||
// - `Buffer`: Shim of `Buffer` implementation
|
||||
// - `global`: The window object, which is aliased to `global` by webpack.
|
||||
function runPreloadScript(context: PreloadContext, preloadSrc: string) {
|
||||
function runPreloadScript (context: PreloadContext, preloadSrc: string) {
|
||||
const globalVariables = [];
|
||||
const fnParameters = [];
|
||||
for (const [key, value] of Object.entries(context.exposeGlobals)) {
|
||||
@@ -84,10 +84,7 @@ function runPreloadScript(context: PreloadContext, preloadSrc: string) {
|
||||
/**
|
||||
* Execute preload scripts within a sandboxed process.
|
||||
*/
|
||||
export function executeSandboxedPreloadScripts(
|
||||
context: PreloadContext,
|
||||
preloadScripts: ElectronInternal.PreloadScript[]
|
||||
) {
|
||||
export function executeSandboxedPreloadScripts (context: PreloadContext, preloadScripts: ElectronInternal.PreloadScript[]) {
|
||||
for (const { filePath, contents, error } of preloadScripts) {
|
||||
try {
|
||||
if (contents) {
|
||||
|
||||
@@ -5,14 +5,11 @@ import type { ClientRequestConstructorOptions, IncomingMessage } from 'electron/
|
||||
|
||||
const { isOnline, resolveHost } = process._linkedBinding('electron_common_net');
|
||||
|
||||
export function request(
|
||||
options: ClientRequestConstructorOptions | string,
|
||||
callback?: (message: IncomingMessage) => void
|
||||
) {
|
||||
export function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
|
||||
return new ClientRequest(options, callback);
|
||||
}
|
||||
|
||||
export function fetch(input: RequestInfo, init?: RequestInit): Promise<Response> {
|
||||
export function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
|
||||
return fetchWithSession(input, init, undefined, request);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,9 +36,7 @@ parentPort.on('removeListener', (name: string) => {
|
||||
});
|
||||
|
||||
// Finally load entry script.
|
||||
const { runEntryPointWithESMLoader } = __non_webpack_require__(
|
||||
'internal/modules/run_main'
|
||||
) as typeof import('@node/lib/internal/modules/run_main');
|
||||
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main') as typeof import('@node/lib/internal/modules/run_main');
|
||||
const mainEntry = pathToFileURL(entryScript);
|
||||
|
||||
runEntryPointWithESMLoader(async (cascadedLoader: any) => {
|
||||
|
||||
@@ -6,27 +6,27 @@ const { createParentPort } = process._linkedBinding('electron_utility_parent_por
|
||||
|
||||
export class ParentPort extends EventEmitter implements Electron.ParentPort {
|
||||
#port: ParentPort;
|
||||
constructor() {
|
||||
constructor () {
|
||||
super();
|
||||
this.#port = createParentPort();
|
||||
this.#port.emit = (channel: string | symbol, event: { ports: any[] }) => {
|
||||
if (channel === 'message') {
|
||||
event = { ...event, ports: event.ports.map((p) => new MessagePortMain(p)) };
|
||||
event = { ...event, ports: event.ports.map(p => new MessagePortMain(p)) };
|
||||
}
|
||||
this.emit(channel, event);
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
start(): void {
|
||||
start () : void {
|
||||
this.#port.start();
|
||||
}
|
||||
|
||||
pause(): void {
|
||||
pause () : void {
|
||||
this.#port.pause();
|
||||
}
|
||||
|
||||
postMessage(message: any): void {
|
||||
postMessage (message: any) : void {
|
||||
this.#port.postMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user