Compare commits

..

21 Commits

Author SHA1 Message Date
Electron Bot
12ea1c0c41 Bump v10.0.0-beta.2 2020-06-01 08:32:38 -07:00
Shelley Vohr
9d2aa93581 fix: ensure nativeImage serialization main->renderer (#23794)
* refactor: use typeutils for nativeImage serialization (#23693)

* fix: ensure nativeImage serialization main->renderer
2020-05-28 12:07:33 -07:00
trop[bot]
cee9e6f0d0 fix: weakly reference MenuModel from MenuController (#23806)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-05-28 09:47:43 -07:00
trop[bot]
eb93acc463 fix: handle asynchronous URL loading in bw proxy (#23804)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-05-28 09:41:54 -07:00
trop[bot]
241e74c098 test: refactor how spec files are collected (#23811)
Co-authored-by: Aleksei Kuzmin <alkuzmin@microsoft.com>
2020-05-28 09:40:59 -07:00
trop[bot]
c0183d15af fix: volume key globalShortcut registration (#23823)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-05-28 08:57:25 -07:00
trop[bot]
4fe7c9ac24 fix: only bezel frameless windows (#23809)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-05-28 08:52:58 -04:00
Cheng Zhao
50efa847a5 Revert "fix: trigger activate event when app is activated via app switcher (#23771)" (#23819)
This reverts commit 7709e600c6.
2020-05-28 08:47:55 -04:00
trop[bot]
c2354d44ea fix: pass correct buffer length (#23798)
Co-authored-by: Cheng Zhao <zcbenz@gmail.com>
2020-05-27 14:09:30 -07:00
trop[bot]
7709e600c6 fix: trigger activate event when app is activated via app switcher (#23771)
When application is activated thru macOS app switcher (cmd+tab) the
App's activate event is note emitted. The reason is that
`applicationShouldHandleReopen:hasVisibleWindows:` is sent only when app
is activated via Dock. Using `applicationDidBecomeActive:` is handling
all cases properly.

Co-authored-by: Lukas Weber <luweber@microsoft.com>
2020-05-27 09:52:10 +09:00
trop[bot]
0962c1bd74 ci: deflake WOA tests (#23769)
Co-authored-by: John Kleinschmidt <jkleinsc@github.com>
2020-05-26 13:22:55 -04:00
trop[bot]
471f80521d test: use WebContents event to test beforeunload (#23766)
Co-authored-by: Cheng Zhao <zcbenz@gmail.com>
2020-05-26 10:53:23 -04:00
trop[bot]
1fb11e1e76 fix: trigger about panel for about role on on win (#23717)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2020-05-22 00:55:25 -07:00
trop[bot]
f8508b3c18 fix: read GTK dark theme setting on Linux (#23711)
Co-authored-by: Cheng Zhao <zcbenz@gmail.com>
2020-05-21 21:07:42 -04:00
Electron Bot
95e3853b77 Bump v10.0.0-beta.1 2020-05-21 14:28:15 -07:00
Samuel Attard
9de5ede1fb Revert "feat: look harder for a commit's pull request. (#23593)"
This reverts commit 2342aaffbd.
2020-05-21 14:27:04 -07:00
Electron Bot
05efbbcdd5 Revert "Bump v10.0.0-beta.1"
This reverts commit 2789f32efb.
2020-05-21 14:23:22 -07:00
Electron Bot
2789f32efb Bump v10.0.0-beta.1 2020-05-21 14:23:00 -07:00
Samuel Attard
4c8b884998 fix: support 10-x-y in the release notes generator (#23709) 2020-05-21 14:13:17 -07:00
Electron Bot
03ddd2d7af Revert "Bump v10.0.0-beta.1"
This reverts commit c141b1a906.
2020-05-21 13:33:35 -07:00
Electron Bot
c141b1a906 Bump v10.0.0-beta.1 2020-05-21 13:33:02 -07:00
558 changed files with 8717 additions and 14563 deletions

View File

@@ -418,7 +418,6 @@ step-electron-build: &step-electron-build
fi
cd src
ninja -C out/Default electron -j $NUMBER_OF_NINJA_PROCESSES
node electron/script/check-symlinks.js
step-native-unittests-build: &step-native-unittests-build
run:
@@ -707,7 +706,6 @@ step-hunspell-store: &step-hunspell-store
step-maybe-generate-breakpad-symbols: &step-maybe-generate-breakpad-symbols
run:
name: Generate breakpad symbols
no_output_timeout: 30m
command: |
if [ "$GENERATE_SYMBOLS" == "true" ]; then
cd src
@@ -832,7 +830,7 @@ step-restore-out-cache: &step-restore-out-cache
paths:
- ./src/out/Default
keys:
- v9-out-cache-{{ checksum "src/electron/.depshash" }}-{{ checksum "src/electron/.depshash-target" }}
- v7-out-cache-{{ checksum "src/electron/.depshash" }}-{{ checksum "src/electron/.depshash-target" }}
name: Restoring out cache
step-set-git-cache-path: &step-set-git-cache-path
@@ -856,7 +854,7 @@ step-save-out-cache: &step-save-out-cache
save_cache:
paths:
- ./src/out/Default
key: v9-out-cache-{{ checksum "src/electron/.depshash" }}-{{ checksum "src/electron/.depshash-target" }}
key: v7-out-cache-{{ checksum "src/electron/.depshash" }}-{{ checksum "src/electron/.depshash-target" }}
name: Persisting out cache
step-run-electron-only-hooks: &step-run-electron-only-hooks

View File

@@ -15,7 +15,6 @@ Contributors guide: https://github.com/electron/electron/blob/master/CONTRIBUTIN
- [ ] relevant documentation is changed or added
- [ ] PR title follows semantic [commit guidelines](https://github.com/electron/electron/blob/master/docs/development/pull-requests.md#commit-message-guidelines)
- [ ] [PR release notes](https://github.com/electron/clerk/blob/master/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/master/README.md#examples).
- [ ] This is **NOT A BREAKING CHANGE**. Breaking changes may not be merged to master until 11-x-y is branched.
#### Release Notes

125
BUILD.gn
View File

@@ -322,10 +322,10 @@ source_set("electron_lib") {
"shell/common/api:mojo",
"//base:base_static",
"//base/allocator:buildflags",
"//chrome/app:command_ids",
"//chrome/app/resources:platform_locale_settings",
"//chrome/services/printing/public/mojom",
"//components/certificate_transparency",
"//components/crash/core/app",
"//components/language/core/browser",
"//components/net_log",
"//components/network_hints/browser",
@@ -347,7 +347,6 @@ source_set("electron_lib") {
"//device/bluetooth",
"//device/bluetooth/public/cpp",
"//gin",
"//media/blink:blink",
"//media/capture/mojom:video_capture",
"//media/mojo/mojom",
"//net:extras",
@@ -433,9 +432,6 @@ source_set("electron_lib") {
"*\bviews/*",
]
}
if (!is_mas_build) {
deps += [ "//components/crash/core/app" ]
}
set_sources_assignment_filter(
sources_assignment_filter + extra_source_filters)
@@ -461,13 +457,10 @@ source_set("electron_lib") {
deps += [
"//components/remote_cocoa/app_shim",
"//content/common:mac_helpers",
"//third_party/crashpad/crashpad/client",
"//ui/accelerated_widget_mac",
]
if (!is_mas_build) {
deps += [ "//third_party/crashpad/crashpad/client" ]
}
libs = [
"AVFoundation.framework",
"Carbon.framework",
@@ -488,26 +481,19 @@ source_set("electron_lib") {
sources += [ "shell/browser/api/electron_api_app_mas.mm" ]
sources -= [ "shell/browser/auto_updater_mac.mm" ]
defines += [ "MAS_BUILD" ]
sources -= [
"shell/app/electron_crash_reporter_client.cc",
"shell/app/electron_crash_reporter_client.h",
"shell/common/crash_keys.cc",
"shell/common/crash_keys.h",
]
} else {
libs += [
"Squirrel.framework",
"ReactiveObjC.framework",
"ReactiveCocoa.framework",
"Mantle.framework",
]
deps += [
"//third_party/squirrel.mac:reactiveobjc_framework+link",
"//third_party/squirrel.mac:squirrel_framework+link",
cflags_objcc = [
"-F",
rebase_path("external_binaries", root_build_dir),
]
# ReactiveObjC which is used by Squirrel requires using __weak.
cflags_objcc = [ "-fobjc-weak" ]
# ReactiveCocoa which is used by Squirrel requires using __weak.
cflags_objcc += [ "-fobjc-weak" ]
}
}
if (is_linux) {
@@ -537,16 +523,6 @@ source_set("electron_lib") {
sources += filenames.lib_sources_nss
sources += [
"shell/browser/ui/gtk/app_indicator_icon.cc",
"shell/browser/ui/gtk/app_indicator_icon.h",
"shell/browser/ui/gtk/app_indicator_icon_menu.cc",
"shell/browser/ui/gtk/app_indicator_icon_menu.h",
"shell/browser/ui/gtk/gtk_status_icon.cc",
"shell/browser/ui/gtk/gtk_status_icon.h",
"shell/browser/ui/gtk/menu_util.cc",
"shell/browser/ui/gtk/menu_util.h",
"shell/browser/ui/gtk/status_icon.cc",
"shell/browser/ui/gtk/status_icon.h",
"shell/browser/ui/gtk_util.cc",
"shell/browser/ui/gtk_util.h",
]
@@ -603,6 +579,15 @@ source_set("electron_lib") {
]
}
if (enable_remote_module) {
sources += [
"shell/common/api/remote/remote_callback_freer.cc",
"shell/common/api/remote/remote_callback_freer.h",
"shell/common/api/remote/remote_object_freer.cc",
"shell/common/api/remote/remote_object_freer.h",
]
}
if (enable_desktop_capturer) {
if (is_component_build && !is_linux) {
# On windows the implementation relies on unexported
@@ -778,10 +763,8 @@ if (is_mac) {
framework_contents = [
"Resources",
"Libraries",
"Helpers",
]
if (!is_mas_build) {
framework_contents += [ "Helpers" ]
}
public_deps = [
":electron_framework_libraries",
":electron_lib",
@@ -812,6 +795,8 @@ if (is_mac) {
}
ldflags = [
"-F",
rebase_path("external_binaries", root_build_dir),
"-Wl,-install_name,@rpath/$output_name.framework/$output_name",
"-rpath",
"@loader_path/Libraries",
@@ -862,49 +847,17 @@ if (is_mac) {
}
}
template("stripped_framework") {
action(target_name) {
assert(defined(invoker.framework))
script = "//electron/build/strip_framework.py"
forward_variables_from(invoker, [ "deps" ])
inputs = [ "$root_out_dir/" + invoker.framework ]
outputs = [ "$target_out_dir/stripped_frameworks/" + invoker.framework ]
args = rebase_path(inputs) + rebase_path(outputs)
}
}
stripped_framework("stripped_mantle_framework") {
framework = "Mantle.framework"
deps = [ "//third_party/squirrel.mac:mantle_framework" ]
}
stripped_framework("stripped_reactiveobjc_framework") {
framework = "ReactiveObjC.framework"
deps = [ "//third_party/squirrel.mac:reactiveobjc_framework" ]
}
stripped_framework("stripped_squirrel_framework") {
framework = "Squirrel.framework"
deps = [ "//third_party/squirrel.mac:squirrel_framework" ]
}
bundle_data("electron_app_framework_bundle_data") {
sources = [ "$root_out_dir/$electron_framework_name.framework" ]
if (!is_mas_build) {
sources += get_target_outputs(":stripped_mantle_framework") +
get_target_outputs(":stripped_reactiveobjc_framework") +
get_target_outputs(":stripped_squirrel_framework")
sources += [
"external_binaries/Mantle.framework",
"external_binaries/ReactiveCocoa.framework",
"external_binaries/Squirrel.framework",
]
}
outputs = [ "{{bundle_contents_dir}}/Frameworks/{{source_file_part}}" ]
public_deps = [
":electron_framework+link",
":stripped_mantle_framework",
":stripped_reactiveobjc_framework",
":stripped_squirrel_framework",
]
public_deps = [ ":electron_framework+link" ]
foreach(helper_params, content_mac_helpers) {
sources +=
@@ -1042,16 +995,13 @@ if (is_mac) {
group("electron_symbols") {
deps = [
":crashpad_handler_syms",
":electron_app_syms",
":electron_framework_syms",
":swiftshader_egl_syms",
":swiftshader_gles_syms",
]
if (!is_mas_build) {
deps += [ ":crashpad_handler_syms" ]
}
foreach(helper_params, content_mac_helpers) {
_helper_target = helper_params[0]
deps += [ ":electron_helper_syms_${_helper_target}" ]
@@ -1102,9 +1052,7 @@ if (is_mac) {
data += [ "$root_out_dir/resources/default_app.asar" ]
}
if (use_v8_context_snapshot) {
public_deps = [ "//tools/v8_context_snapshot:v8_context_snapshot" ]
}
public_deps = [ "//tools/v8_context_snapshot:v8_context_snapshot" ]
if (is_win) {
sources += [
@@ -1212,6 +1160,18 @@ test("shell_browser_ui_unittests") {
"//ui/base",
"//ui/strings",
]
if (is_mac) {
# Resolve paths owing to different test executable locations
ldflags = [
"-F",
rebase_path("external_binaries", root_build_dir),
"-rpath",
"@loader_path",
"-rpath",
"@executable_path/" + rebase_path("external_binaries", root_build_dir),
]
}
}
template("dist_zip") {
@@ -1309,13 +1269,10 @@ dist_zip("electron_chromedriver_zip") {
mksnapshot_deps = [
":licenses",
"//tools/v8_context_snapshot:v8_context_snapshot_generator($v8_snapshot_toolchain)",
"//v8:mksnapshot($v8_snapshot_toolchain)",
]
if (use_v8_context_snapshot) {
mksnapshot_deps += [ "//tools/v8_context_snapshot:v8_context_snapshot_generator($v8_snapshot_toolchain)" ]
}
group("electron_mksnapshot") {
public_deps = mksnapshot_deps
}

30
DEPS
View File

@@ -5,7 +5,6 @@ gclient_gn_args = [
'checkout_android_native_support',
'checkout_libaom',
'checkout_nacl',
'checkout_pgo_profiles',
'checkout_oculus_sdk',
'checkout_openxr',
'checkout_google_benchmark'
@@ -13,13 +12,11 @@ gclient_gn_args = [
vars = {
'chromium_version':
'9ae03ef8f7d4f6ac663f725bcfe70311987652f3',
'17da1064b910f4170320ff37e971064916aed1dc',
'node_version':
'v12.18.2',
'v12.16.3',
'nan_version':
'2c4ee8a32a299eada3cd6e468bbd0a473bfea96d',
'squirrel.mac_version':
'44468f858ce0d25c27bd5e674abfa104e0119738',
'boto_version': 'f7574aa6cc2c819430c1f05e9a1a1a666ef8169b',
'pyyaml_version': '3.12',
@@ -31,7 +28,6 @@ vars = {
'nodejs_git': 'https://github.com/nodejs',
'requests_git': 'https://github.com/kennethreitz',
'yaml_git': 'https://github.com/yaml',
'squirrel_git': 'https://github.com/Squirrel',
# KEEP IN SYNC WITH utils.js FILE
'yarn_version': '1.15.2',
@@ -46,7 +42,6 @@ vars = {
'checkout_chromium': True,
'checkout_node': True,
'checkout_nan': True,
'checkout_pgo_profiles': True,
# It's only needed to parse the native tests configurations.
'checkout_pyyaml': False,
@@ -104,18 +99,6 @@ deps = {
'url': Var('requests_git') + '/requests.git' + '@' + Var('requests_version'),
'condition': 'checkout_requests and process_deps',
},
'src/third_party/squirrel.mac': {
'url': Var("squirrel_git") + '/Squirrel.Mac.git@' + Var("squirrel.mac_version"),
'condition': 'process_deps',
},
'src/third_party/squirrel.mac/vendor/ReactiveObjC': {
'url': 'https://github.com/ReactiveCocoa/ReactiveObjC.git@74ab5baccc6f7202c8ac69a8d1e152c29dc1ea76',
'condition': 'process_deps'
},
'src/third_party/squirrel.mac/vendor/Mantle': {
'url': 'https://github.com/Mantle/Mantle.git@78d3966b3c331292ea29ec38661b25df0a245948',
'condition': 'process_deps',
}
}
hooks = [
@@ -124,7 +107,7 @@ hooks = [
'condition': '(checkout_chromium and apply_patches) and process_deps',
'pattern': 'src/electron',
'action': [
'python3',
'python',
'src/electron/script/apply_all_patches.py',
'src/electron/patches/config.json',
],
@@ -142,7 +125,7 @@ hooks = [
'name': 'electron_npm_deps',
'pattern': 'src/electron/package.json',
'action': [
'python3',
'python',
'-c',
'import os, subprocess; os.chdir(os.path.join("src", "electron")); subprocess.check_call(["python", "script/lib/npx.py", "yarn@' + (Var("yarn_version")) + '", "install", "--frozen-lockfile"]);',
],
@@ -152,7 +135,7 @@ hooks = [
'pattern': 'src/electron',
'condition': 'checkout_boto and process_deps',
'action': [
'python3',
'python',
'-c',
'import os, subprocess; os.chdir(os.path.join("src", "electron", "vendor", "boto")); subprocess.check_call(["python", "setup.py", "build"]);',
],
@@ -162,7 +145,7 @@ hooks = [
'pattern': 'src/electron',
'condition': 'checkout_requests and process_deps',
'action': [
'python3',
'python',
'-c',
'import os, subprocess; os.chdir(os.path.join("src", "electron", "vendor", "requests")); subprocess.check_call(["python", "setup.py", "build"]);',
],
@@ -171,5 +154,4 @@ hooks = [
recursedeps = [
'src',
'src/third_party/squirrel.mac',
]

View File

@@ -1 +1 @@
11.0.0-nightly.20200708
10.0.0-beta.2

View File

@@ -1,16 +0,0 @@
#!/usr/bin/env python
import os
import subprocess
import sys
source = sys.argv[1]
dest = sys.argv[2]
# Ensure any existing framework is removed
subprocess.check_output(["rm", "-rf", dest])
subprocess.check_output(["cp", "-a", source, dest])
# Strip headers, we do not need to ship them
subprocess.check_output(["rm", "-r", os.path.join(dest, 'Headers')])
subprocess.check_output(["rm", "-r", os.path.join(dest, 'Versions', 'Current', 'Headers')])

View File

@@ -1,4 +1,3 @@
const fs = require('fs');
const path = require('path')
const webpack = require('webpack')
@@ -10,9 +9,6 @@ config.output = {
filename: path.basename(outPath)
}
const { wrapInitWithProfilingTimeout } = config;
delete config.wrapInitWithProfilingTimeout;
webpack(config, (err, stats) => {
if (err) {
console.error(err)
@@ -21,18 +17,6 @@ webpack(config, (err, stats) => {
console.error(stats.toString('normal'))
process.exit(1)
} else {
if (wrapInitWithProfilingTimeout) {
const contents = fs.readFileSync(outPath, 'utf8');
const newContents = `function ___electron_webpack_init__() {
${contents}
};
if ((globalThis.process || binding.process).argv.includes("--profile-electron-init")) {
setTimeout(___electron_webpack_init__, 0);
} else {
___electron_webpack_init__();
}`;
fs.writeFileSync(outPath, newContents);
}
process.exit(0)
}
})

View File

@@ -1,7 +1,6 @@
const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
const TerserPlugin = require('terser-webpack-plugin');
const electronRoot = path.resolve(__dirname, '../..')
@@ -22,7 +21,7 @@ class AccessDependenciesPlugin {
}
const defines = {
BUILDFLAG: onlyPrintingGraph ? '(a => a)' : ''
BUILDFLAG: ''
}
const buildFlagsPrefix = '--buildflags='
@@ -35,7 +34,8 @@ if (buildFlagArg) {
for (const line of flagFile.split(/(\r\n|\r|\n)/g)) {
const flagMatch = line.match(/#define BUILDFLAG_INTERNAL_(.+?)\(\) \(([01])\)/)
if (flagMatch) {
const [, flagName, flagValue] = flagMatch;
const flagName = flagMatch[1];
const flagValue = flagMatch[2];
defines[flagName] = JSON.stringify(Boolean(parseInt(flagValue, 10)));
}
}
@@ -64,12 +64,16 @@ if (defines['ENABLE_VIEWS_API'] === 'false') {
)
}
const alias = {}
for (const ignoredModule of ignoredModules) {
alias[ignoredModule] = path.resolve(electronRoot, 'lib/common/dummy.js')
}
module.exports = ({
alwaysHasNode,
loadElectronFromAlternateTarget,
targetDeletesNodeGlobals,
target,
wrapInitWithProfilingTimeout
target
}) => {
let entry = path.resolve(electronRoot, 'lib', target, 'init.ts')
if (!fs.existsSync(entry)) {
@@ -78,36 +82,30 @@ module.exports = ({
return ({
mode: 'development',
devtool: false,
devtool: 'inline-source-map',
entry,
target: alwaysHasNode ? 'node' : 'web',
output: {
filename: `${target}.bundle.js`
},
wrapInitWithProfilingTimeout,
resolve: {
alias: {
...alias,
'@electron/internal': path.resolve(electronRoot, 'lib'),
'electron': path.resolve(electronRoot, 'lib', loadElectronFromAlternateTarget || target, 'api', 'exports', 'electron.ts'),
// Force timers to resolve to our dependency that doesn't use window.postMessage
// Force timers to resolve to our dependency that doens't use window.postMessage
'timers': path.resolve(electronRoot, 'node_modules', 'timers-browserify', 'main.js')
},
extensions: ['.ts', '.js']
},
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,
]
ignoreDiagnostics: [6059]
}
}]
},
@@ -118,17 +116,6 @@ module.exports = ({
// one of our renderer bundles should import it from the 'timers' package
setImmediate: false,
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
keep_classnames: true,
keep_fnames: true,
},
}),
],
},
plugins: [
new AccessDependenciesPlugin(),
...(targetDeletesNodeGlobals ? [

View File

@@ -1,6 +1,5 @@
module.exports = require('./webpack.config.base')({
target: 'renderer',
alwaysHasNode: true,
targetDeletesNodeGlobals: true,
wrapInitWithProfilingTimeout: true
targetDeletesNodeGlobals: true
})

View File

@@ -1,5 +1,4 @@
module.exports = require('./webpack.config.base')({
target: 'sandboxed_renderer',
alwaysHasNode: false,
wrapInitWithProfilingTimeout: true,
alwaysHasNode: false
})

View File

@@ -16,7 +16,6 @@ template("webpack_build") {
inputs = [
invoker.config_file,
"//electron/build/webpack/webpack.config.base.js",
"//electron/build/webpack/run-compiler.js",
"//electron/tsconfig.json",
"//electron/yarn.lock",
"//electron/typings/internal-ambient.d.ts",

View File

@@ -72,7 +72,6 @@ static_library("chrome") {
]
deps = [
"//chrome/browser:resource_prefetch_predictor_proto",
"//chrome/services/speech:buildflags",
"//components/feature_engagement:buildflags",
"//components/optimization_guide/proto:optimization_guide_proto",
]

View File

@@ -68,6 +68,7 @@
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram_macros.h"
#include "base/path_service.h"
#include "base/posix/eintr_wrapper.h"

View File

@@ -24,18 +24,18 @@ GlobalMenuBarRegistrarX11* GlobalMenuBarRegistrarX11::GetInstance() {
return base::Singleton<GlobalMenuBarRegistrarX11>::get();
}
void GlobalMenuBarRegistrarX11::OnWindowMapped(x11::Window window) {
live_windows_.insert(window);
void GlobalMenuBarRegistrarX11::OnWindowMapped(unsigned long xid) {
live_xids_.insert(xid);
if (registrar_proxy_)
RegisterXWindow(window);
RegisterXID(xid);
}
void GlobalMenuBarRegistrarX11::OnWindowUnmapped(x11::Window window) {
void GlobalMenuBarRegistrarX11::OnWindowUnmapped(unsigned long xid) {
if (registrar_proxy_)
UnregisterXWindow(window);
UnregisterXID(xid);
live_windows_.erase(window);
live_xids_.erase(xid);
}
GlobalMenuBarRegistrarX11::GlobalMenuBarRegistrarX11()
@@ -63,9 +63,9 @@ GlobalMenuBarRegistrarX11::~GlobalMenuBarRegistrarX11() {
}
}
void GlobalMenuBarRegistrarX11::RegisterXWindow(x11::Window window) {
void GlobalMenuBarRegistrarX11::RegisterXID(unsigned long xid) {
DCHECK(registrar_proxy_);
std::string path = electron::GlobalMenuBarX11::GetPathForWindow(window);
std::string path = electron::GlobalMenuBarX11::GetPathForWindow(xid);
ANNOTATE_SCOPED_MEMORY_LEAK; // http://crbug.com/314087
// TODO(erg): The mozilla implementation goes to a lot of callback trouble
@@ -76,13 +76,13 @@ void GlobalMenuBarRegistrarX11::RegisterXWindow(x11::Window window) {
// I don't see any reason why we should care if "RegisterWindow" completes or
// not.
g_dbus_proxy_call(registrar_proxy_, "RegisterWindow",
g_variant_new("(uo)", window, path.c_str()),
g_variant_new("(uo)", xid, path.c_str()),
G_DBUS_CALL_FLAGS_NONE, -1, nullptr, nullptr, nullptr);
}
void GlobalMenuBarRegistrarX11::UnregisterXWindow(x11::Window window) {
void GlobalMenuBarRegistrarX11::UnregisterXID(unsigned long xid) {
DCHECK(registrar_proxy_);
std::string path = electron::GlobalMenuBarX11::GetPathForWindow(window);
std::string path = electron::GlobalMenuBarX11::GetPathForWindow(xid);
ANNOTATE_SCOPED_MEMORY_LEAK; // http://crbug.com/314087
// TODO(erg): The mozilla implementation goes to a lot of callback trouble
@@ -93,7 +93,7 @@ void GlobalMenuBarRegistrarX11::UnregisterXWindow(x11::Window window) {
// I don't see any reason why we should care if "UnregisterWindow" completes
// or not.
g_dbus_proxy_call(registrar_proxy_, "UnregisterWindow",
g_variant_new("(u)", window), G_DBUS_CALL_FLAGS_NONE, -1,
g_variant_new("(u)", xid), G_DBUS_CALL_FLAGS_NONE, -1,
nullptr, nullptr, nullptr);
}
@@ -120,10 +120,10 @@ void GlobalMenuBarRegistrarX11::OnProxyCreated(GObject* source,
void GlobalMenuBarRegistrarX11::OnNameOwnerChanged(GObject* /* ignored */,
GParamSpec* /* ignored */) {
// If the name owner changed, we need to reregister all the live x11::Window
// with the system.
for (std::set<x11::Window>::const_iterator it = live_windows_.begin();
it != live_windows_.end(); ++it) {
RegisterXWindow(*it);
// If the name owner changed, we need to reregister all the live xids with
// the system.
for (std::set<unsigned long>::const_iterator it = live_xids_.begin();
it != live_xids_.end(); ++it) {
RegisterXID(*it);
}
}

View File

@@ -12,20 +12,19 @@
#include "base/memory/ref_counted.h"
#include "base/memory/singleton.h"
#include "ui/base/glib/glib_signal.h"
#include "ui/gfx/x/xproto.h"
// Advertises our menu bars to Unity.
//
// GlobalMenuBarX11 is responsible for managing the DbusmenuServer for each
// x11::Window. We need a separate object to own the dbus channel to
// XID. We need a separate object to own the dbus channel to
// com.canonical.AppMenu.Registrar and to register/unregister the mapping
// between a x11::Window and the DbusmenuServer instance we are offering.
// between a XID and the DbusmenuServer instance we are offering.
class GlobalMenuBarRegistrarX11 {
public:
static GlobalMenuBarRegistrarX11* GetInstance();
void OnWindowMapped(x11::Window window);
void OnWindowUnmapped(x11::Window window);
void OnWindowMapped(unsigned long xid);
void OnWindowUnmapped(unsigned long xid);
private:
friend struct base::DefaultSingletonTraits<GlobalMenuBarRegistrarX11>;
@@ -34,8 +33,8 @@ class GlobalMenuBarRegistrarX11 {
~GlobalMenuBarRegistrarX11();
// Sends the actual message.
void RegisterXWindow(x11::Window window);
void UnregisterXWindow(x11::Window window);
void RegisterXID(unsigned long xid);
void UnregisterXID(unsigned long xid);
CHROMEG_CALLBACK_1(GlobalMenuBarRegistrarX11,
void,
@@ -50,9 +49,9 @@ class GlobalMenuBarRegistrarX11 {
GDBusProxy* registrar_proxy_;
// x11::Window which want to be registered, but haven't yet been because
// Window XIDs which want to be registered, but haven't yet been because
// we're waiting for the proxy to become available.
std::set<x11::Window> live_windows_;
std::set<unsigned long> live_xids_;
DISALLOW_COPY_AND_ASSIGN(GlobalMenuBarRegistrarX11);
};

View File

@@ -1,6 +1,5 @@
import { app, dialog, BrowserWindow, shell, ipcMain } from 'electron';
import * as path from 'path';
import * as url from 'url';
let mainWindow: BrowserWindow | null = null;
@@ -30,11 +29,12 @@ function isTrustedSender (webContents: Electron.WebContents) {
return false;
}
try {
return url.fileURLToPath(webContents.getURL()) === indexPath;
} catch {
return false;
}
const parsedUrl = new URL(webContents.getURL());
const urlPath = process.platform === 'win32'
// Strip the prefixed "/" that occurs on windows
? path.resolve(parsedUrl.pathname.substr(1))
: parsedUrl.pathname;
return parsedUrl.protocol === 'file:' && urlPath === indexPath;
}
ipcMain.handle('bootstrap', (event) => {

View File

@@ -1,9 +1,8 @@
import * as electron from 'electron';
import { app, dialog } from 'electron';
import * as fs from 'fs';
import * as path from 'path';
import * as url from 'url';
const { app, dialog } = electron;
type DefaultAppOptions = {
file: null | string;
@@ -146,90 +145,13 @@ function startRepl () {
process.exit(1);
}
// Prevent quitting.
// prevent quitting
app.on('window-all-closed', () => {});
const GREEN = '32';
const colorize = (color: string, s: string) => `\x1b[${color}m${s}\x1b[0m`;
const electronVersion = colorize(GREEN, `v${process.versions.electron}`);
const nodeVersion = colorize(GREEN, `v${process.versions.node}`);
console.info(`
Welcome to the Electron.js REPL \\[._.]/
You can access all Electron.js modules here as well as Node.js modules.
Using: Node.js ${nodeVersion} and Electron.js ${electronVersion}
`);
const { REPLServer } = require('repl');
const repl = new REPLServer({
prompt: '> '
}).on('exit', () => {
const repl = require('repl');
repl.start('> ').on('exit', () => {
process.exit(0);
});
function defineBuiltin (context: any, name: string, getter: Function) {
const setReal = (val: any) => {
// Deleting the property before re-assigning it disables the
// getter/setter mechanism.
delete context[name];
context[name] = val;
};
Object.defineProperty(context, name, {
get: () => {
const lib = getter();
delete context[name];
Object.defineProperty(context, name, {
get: () => lib,
set: setReal,
configurable: true,
enumerable: false
});
return lib;
},
set: setReal,
configurable: true,
enumerable: false
});
}
defineBuiltin(repl.context, 'electron', () => electron);
for (const api of Object.keys(electron) as (keyof typeof electron)[]) {
defineBuiltin(repl.context, api, () => electron[api]);
}
// Copied from node/lib/repl.js. For better DX, we don't want to
// show e.g 'contentTracing' at a higher priority than 'const', so
// 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'
];
const electronBuiltins = [...Object.keys(electron), 'original-fs', 'electron'];
const defaultComplete = repl.completer;
repl.completer = (line: string, callback: Function) => {
const lastSpace = line.lastIndexOf(' ');
const currentSymbol = line.substring(lastSpace + 1, repl.cursor);
const filterFn = (c: string) => c.startsWith(currentSymbol);
const ignores = commonWords.filter(filterFn);
const hits = electronBuiltins.filter(filterFn);
if (!ignores.length && hits.length) {
callback(null, [hits, currentSymbol]);
} else {
defaultComplete.apply(repl, [line, callback]);
}
};
}
// Start the specified app if there is one specified in command line, otherwise

View File

@@ -92,6 +92,7 @@ These individual tutorials expand on topics discussed in the guide above.
* Electron Releases & Developer Feedback
* [Versioning Policy](tutorial/electron-versioning.md)
* [Release Timelines](tutorial/electron-timelines.md)
* [App Feedback Program](tutorial/app-feedback-program.md)
* [Packaging App Source Code with asar](tutorial/application-packaging.md)
* [Generating asar Archives](tutorial/application-packaging.md#generating-asar-archives)
* [Using asar Archives](tutorial/application-packaging.md#using-asar-archives)

View File

@@ -140,16 +140,6 @@ this event, such as launching the application for the first time, attempting
to re-launch the application when it's already running, or clicking on the
application's dock or taskbar icon.
### Event: 'did-become-active' _macOS_
Returns:
* `event` Event
Emitted when mac application become active. Difference from `activate` event is
that `did-become-active` is emitted every time the app becomes active, not only
when Dock icon is clicked or application is re-launched.
### Event: 'continue-activity' _macOS_
Returns:
@@ -369,7 +359,7 @@ Returns:
Emitted when the GPU process crashes or is killed.
### Event: 'renderer-process-crashed' _Deprecated_
### Event: 'renderer-process-crashed'
Returns:
@@ -815,20 +805,6 @@ Returns `String` - Name of the application handling the protocol, or an empty
This method returns the application name of the default handler for the protocol
(aka URI scheme) of a URL.
### `app.getApplicationInfoForProtocol(url)` _macOS_ _Windows_
* `url` String - a URL with the protocol name to check. Unlike the other
methods in this family, this accepts an entire URL, including `://` at a
minimum (e.g. `https://`).
Returns `Promise<Object>` - Resolve with an object containing the following:
* `icon` NativeImage - the display icon of the app handling the protocol.
* `path` String - installation path of the app handling the protocol.
* `name` String - display name of the app handling the protocol.
This method returns a promise that contains the application name, icon and path of the default handler for the protocol
(aka URI scheme) of a URL.
### `app.setUserTasks(tasks)` _Windows_
* `tasks` [Task[]](structures/task.md) - Array of `Task` objects
@@ -1246,7 +1222,7 @@ Show the app's about panel options. These options can be overridden with `app.se
* `credits` String (optional) _macOS_ _Windows_ - Credit information.
* `authors` String[] (optional) _Linux_ - List of app authors.
* `website` String (optional) _Linux_ - The app's website.
* `iconPath` String (optional) _Linux_ _Windows_ - Path to the app's icon in a JPEG or PNG file format. On Linux, will be shown as 64x64 pixels while retaining aspect ratio.
* `iconPath` String (optional) _Linux_ _Windows_ - Path to the app's icon. On Linux, will be shown as 64x64 pixels while retaining aspect ratio.
Set the about panel options. This will override the values defined in the app's `.plist` file on macOS. See the [Apple docs][about-panel-options] for more details. On Linux, values must be set in order to be shown; there are no defaults.
@@ -1427,7 +1403,7 @@ in your app's initialization to ensure that your overridden value is used.
A `Boolean` which when `true` disables the overrides that Electron has in place
to ensure renderer processes are restarted on every navigation. The current
default value for this property is `true`.
default value for this property is `false`.
The intention is for these overrides to become disabled by default and then at
some point in the future this property will be removed. This property impacts

View File

@@ -387,13 +387,6 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
Default is `true`.
* `enableWebSQL` Boolean (optional) - Whether to enable the [WebSQL api](https://www.w3.org/TR/webdatabase/).
Default is `true`.
* `v8CacheOptions` String (optional) - Enforces the v8 code caching policy
used by blink. Accepted values are
* `none` - Disables code caching
* `code` - Heuristic based code caching
* `bypassHeatCheck` - Bypass code caching heuristics but with lazy compilation
* `bypassHeatCheckAndEagerCompile` - Same as above except compilation is eager.
Default policy is `code`.
When setting minimum or maximum window size with `minWidth`/`maxWidth`/
`minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from
@@ -692,7 +685,7 @@ Returns `BrowserWindow | null` - The window that owns the given `browserView`. I
* `id` Integer
Returns `BrowserWindow | null` - The window with the given `id`.
Returns `BrowserWindow` - The window with the given `id`.
#### `BrowserWindow.addExtension(path)` _Deprecated_

View File

@@ -36,10 +36,6 @@ for integrated authentication. Without `*` prefix the URL has to match exactly.
A comma-separated list of servers for which delegation of user credentials is required.
Without `*` prefix the URL has to match exactly.
### --disable-ntlm-v2
Disables NTLM v2 for posix platforms, no effect elsewhere.
### --disable-http-cache
Disables the disk cache for HTTP requests.

View File

@@ -52,8 +52,6 @@ Returns:
* `method` String - Method name.
* `params` any - Event parameters defined by the 'parameters'
attribute in the remote debugging protocol.
* `sessionId` String - Unique identifier of attached debugging session,
will match the value sent from `debugger.sendCommand`.
Emitted whenever the debugging target issues an instrumentation event.
@@ -76,16 +74,11 @@ Returns `Boolean` - Whether a debugger is attached to the `webContents`.
Detaches the debugger from the `webContents`.
#### `debugger.sendCommand(method[, commandParams, sessionId])`
#### `debugger.sendCommand(method[, commandParams])`
* `method` String - Method name, should be one of the methods defined by the
[remote debugging protocol][rdp].
* `commandParams` any (optional) - JSON object with request parameters.
* `sessionId` String (optional) - send command to the target with associated
debugging session id. The initial value can be obtained by sending
[Target.attachToTarget][attachToTarget] message.
[attachToTarget]: https://chromedevtools.github.io/devtools-protocol/tot/Target/#method-attachToTarget
Returns `Promise<any>` - A promise that resolves with the response defined by
the 'returns' attribute of the command description in the remote debugging protocol

View File

@@ -72,50 +72,6 @@ const constraints = {
}
```
This example shows how to capture a video from a [WebContents](web-contents.md)
```javascript
// In the renderer process.
const { desktopCapturer, remote } = require('electron')
desktopCapturer.getMediaSourceIdForWebContents(remote.getCurrentWebContents().id).then(async mediaSourceId => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
mandatory: {
chromeMediaSource: 'tab',
chromeMediaSourceId: mediaSourceId
}
},
video: {
mandatory: {
chromeMediaSource: 'tab',
chromeMediaSourceId: mediaSourceId,
minWidth: 1280,
maxWidth: 1280,
minHeight: 720,
maxHeight: 720
}
}
})
handleStream(stream)
} catch (e) {
handleError(e)
}
})
function handleStream (stream) {
const video = document.querySelector('video')
video.srcObject = stream
video.onloadedmetadata = (e) => video.play()
}
function handleError (e) {
console.log(e)
}
```
## Methods
The `desktopCapturer` module has the following methods:
@@ -138,15 +94,6 @@ Returns `Promise<DesktopCapturerSource[]>` - Resolves with an array of [`Desktop
**Note** Capturing the screen contents requires user consent on macOS 10.15 Catalina or higher,
which can detected by [`systemPreferences.getMediaAccessStatus`].
### `desktopCapturer.getMediaSourceIdForWebContents(webContentsId)`
* `webContentsId` number - Id of the WebContents to get stream of
Returns `Promise<string>` - Resolves with the identifier of a WebContents stream, this identifier can be
used with [`navigator.mediaDevices.getUserMedia`].
The identifier is **only valid for 10 seconds**.
The identifier may be empty if not requested from a renderer process.
[`navigator.mediaDevices.getUserMedia`]: https://developer.mozilla.org/en/docs/Web/API/MediaDevices/getUserMedia
[`systemPreferences.getMediaAccessStatus`]: system-preferences.md#systempreferencesgetmediaaccessstatusmediatype-macos

View File

@@ -12,7 +12,7 @@ See [`Menu`](menu.md) for examples.
* `click` Function (optional) - Will be called with
`click(menuItem, browserWindow, event)` when the menu item is clicked.
* `menuItem` MenuItem
* `browserWindow` [BrowserWindow](browser-window.md) | undefined - This will not be defined if no window is open.
* `browserWindow` [BrowserWindow](browser-window.md)
* `event` [KeyboardEvent](structures/keyboard-event.md)
* `role` String (optional) - Can be `undo`, `redo`, `cut`, `copy`, `paste`, `pasteAndMatchStyle`, `delete`, `selectAll`, `reload`, `forceReload`, `toggleDevTools`, `resetZoom`, `zoomIn`, `zoomOut`, `togglefullscreen`, `window`, `minimize`, `close`, `help`, `about`, `services`, `hide`, `hideOthers`, `unhide`, `quit`, `startSpeaking`, `stopSpeaking`, `zoom`, `front`, `appMenu`, `fileMenu`, `editMenu`, `viewMenu`, `recentDocuments`, `toggleTabBar`, `selectNextTab`, `selectPreviousTab`, `mergeAllWindows`, `clearRecentDocuments`, `moveTabToNewWindow` or `windowMenu` - Define the action of the menu item, when specified the
`click` property will be ignored. See [roles](#roles).

View File

@@ -96,7 +96,7 @@ Appends the `menuItem` to the menu.
* `id` String
Returns `MenuItem | null` the item with the specified `id`
Returns `MenuItem` the item with the specified `id`
#### `menu.insert(pos, menuItem)`

309
docs/api/protocol-ns.md Normal file
View File

@@ -0,0 +1,309 @@
# protocol (NetworkService) (Draft)
This document describes the new protocol APIs based on the [NetworkService](https://www.chromium.org/servicification).
We don't currently have an estimate of when we will enable the `NetworkService` by
default in Electron, but as Chromium is already removing non-`NetworkService`
code, we will probably switch before Electron 10.
The content of this document should be moved to `protocol.md` after we have
enabled the `NetworkService` by default in Electron.
> Register a custom protocol and intercept existing protocol requests.
Process: [Main](../glossary.md#main-process)
An example of implementing a protocol that has the same effect as the
`file://` protocol:
```javascript
const { app, protocol } = require('electron')
const path = require('path')
app.whenReady().then(() => {
protocol.registerFileProtocol('atom', (request, callback) => {
const url = request.url.substr(7)
callback({ path: path.normalize(`${__dirname}/${url}`) })
})
})
```
**Note:** All methods unless specified can only be used after the `ready` event
of the `app` module gets emitted.
## Using `protocol` with a custom `partition` or `session`
A protocol is registered to a specific Electron [`session`](./session.md)
object. If you don't specify a session, then your `protocol` will be applied to
the default session that Electron uses. However, if you define a `partition` or
`session` on your `browserWindow`'s `webPreferences`, then that window will use
a different session and your custom protocol will not work if you just use
`electron.protocol.XXX`.
To have your custom protocol work in combination with a custom session, you need
to register it to that session explicitly.
```javascript
const { session, app, protocol } = require('electron')
const path = require('path')
app.whenReady().then(() => {
const partition = 'persist:example'
const ses = session.fromPartition(partition)
ses.protocol.registerFileProtocol('atom', (request, callback) => {
const url = request.url.substr(7)
callback({ path: path.normalize(`${__dirname}/${url}`) })
})
mainWindow = new BrowserWindow({ webPreferences: { partition } })
})
```
## Methods
The `protocol` module has the following methods:
### `protocol.registerSchemesAsPrivileged(customSchemes)`
* `customSchemes` [CustomScheme[]](structures/custom-scheme.md)
**Note:** This method can only be used before the `ready` event of the `app`
module gets emitted and can be called only once.
Registers the `scheme` as standard, secure, bypasses content security policy for
resources, allows registering ServiceWorker and supports fetch API. Specify a
privilege with the value of `true` to enable the capability.
An example of registering a privileged scheme, that bypasses Content Security
Policy:
```javascript
const { protocol } = require('electron')
protocol.registerSchemesAsPrivileged([
{ scheme: 'foo', privileges: { bypassCSP: true } }
])
```
A standard scheme adheres to what RFC 3986 calls [generic URI
syntax](https://tools.ietf.org/html/rfc3986#section-3). For example `http` and
`https` are standard schemes, while `file` is not.
Registering a scheme as standard allows relative and absolute resources to
be resolved correctly when served. Otherwise the scheme will behave like the
`file` protocol, but without the ability to resolve relative URLs.
For example when you load following page with custom protocol without
registering it as standard scheme, the image will not be loaded because
non-standard schemes can not recognize relative URLs:
```html
<body>
<img src='test.png'>
</body>
```
Registering a scheme as standard will allow access to files through the
[FileSystem API][file-system-api]. Otherwise the renderer will throw a security
error for the scheme.
By default web storage apis (localStorage, sessionStorage, webSQL, indexedDB,
cookies) are disabled for non standard schemes. So in general if you want to
register a custom protocol to replace the `http` protocol, you have to register
it as a standard scheme.
### `protocol.registerFileProtocol(scheme, handler)`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `callback` Function
* `response` (String | [ProtocolResponse](structures/protocol-response.md))
Registers a protocol of `scheme` that will send a file as the response. The
`handler` will be called with `request` and `callback` where `request` is
an incoming request for the `scheme`.
To handle the `request`, the `callback` should be called with either the file's
path or an object that has a `path` property, e.g. `callback(filePath)` or
`callback({ path: filePath })`. The `filePath` must be an absolute path.
By default the `scheme` is treated like `http:`, which is parsed differently
from protocols that follow the "generic URI syntax" like `file:`.
### `protocol.registerBufferProtocol(scheme, handler)`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `callback` Function
* `response` (Buffer | [ProtocolResponse](structures/protocol-response.md))
Registers a protocol of `scheme` that will send a `Buffer` as a response.
The usage is the same with `registerFileProtocol`, except that the `callback`
should be called with either a `Buffer` object or an object that has the `data`
property.
Example:
```javascript
protocol.registerBufferProtocol('atom', (request, callback) => {
callback({ mimeType: 'text/html', data: Buffer.from('<h5>Response</h5>') })
})
```
### `protocol.registerStringProtocol(scheme, handler)`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `callback` Function
* `response` (String | [ProtocolResponse](structures/protocol-response.md))
Registers a protocol of `scheme` that will send a `String` as a response.
The usage is the same with `registerFileProtocol`, except that the `callback`
should be called with either a `String` or an object that has the `data`
property.
### `protocol.registerHttpProtocol(scheme, handler)`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `callback` Function
* `response` ProtocolResponse
Registers a protocol of `scheme` that will send an HTTP request as a response.
The usage is the same with `registerFileProtocol`, except that the `callback`
should be called with an object that has the `url` property.
### `protocol.registerStreamProtocol(scheme, handler)`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `callback` Function
* `response` (ReadableStream | [ProtocolResponse](structures/protocol-response.md))
Registers a protocol of `scheme` that will send a stream as a response.
The usage is the same with `registerFileProtocol`, except that the
`callback` should be called with either a [`ReadableStream`](https://nodejs.org/api/stream.html#stream_class_stream_readable) object or an object that
has the `data` property.
Example:
```javascript
const { protocol } = require('electron')
const { PassThrough } = require('stream')
function createStream (text) {
const rv = new PassThrough() // PassThrough is also a Readable stream
rv.push(text)
rv.push(null)
return rv
}
protocol.registerStreamProtocol('atom', (request, callback) => {
callback({
statusCode: 200,
headers: {
'content-type': 'text/html'
},
data: createStream('<h5>Response</h5>')
})
})
```
It is possible to pass any object that implements the readable stream API (emits
`data`/`end`/`error` events). For example, here's how a file could be returned:
```javascript
protocol.registerStreamProtocol('atom', (request, callback) => {
callback(fs.createReadStream('index.html'))
})
```
### `protocol.unregisterProtocol(scheme)`
* `scheme` String
Unregisters the custom protocol of `scheme`.
### `protocol.isProtocolRegistered(scheme)`
* `scheme` String
Returns `Boolean` - Whether `scheme` is already registered.
### `protocol.interceptFileProtocol(scheme, handler)`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `callback` Function
* `response` (String | [ProtocolResponse](structures/protocol-response.md))
Intercepts `scheme` protocol and uses `handler` as the protocol's new handler
which sends a file as a response.
### `protocol.interceptStringProtocol(scheme, handler)`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `callback` Function
* `response` (String | [ProtocolResponse](structures/protocol-response.md))
Intercepts `scheme` protocol and uses `handler` as the protocol's new handler
which sends a `String` as a response.
### `protocol.interceptBufferProtocol(scheme, handler)`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `callback` Function
* `response` (Buffer | [ProtocolResponse](structures/protocol-response.md))
Intercepts `scheme` protocol and uses `handler` as the protocol's new handler
which sends a `Buffer` as a response.
### `protocol.interceptHttpProtocol(scheme, handler)`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `callback` Function
* `response` ProtocolResponse
Intercepts `scheme` protocol and uses `handler` as the protocol's new handler
which sends a new HTTP request as a response.
### `protocol.interceptStreamProtocol(scheme, handler)`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `callback` Function
* `response` (ReadableStream | [ProtocolResponse](structures/protocol-response.md))
Same as `protocol.registerStreamProtocol`, except that it replaces an existing
protocol handler.
### `protocol.uninterceptProtocol(scheme)`
* `scheme` String
Remove the interceptor installed for `scheme` and restore its original handler.
### `protocol.isProtocolIntercepted(scheme)`
* `scheme` String
Returns `Boolean` - Whether `scheme` is already intercepted.
[file-system-api]: https://developer.mozilla.org/en-US/docs/Web/API/LocalFileSystem

View File

@@ -15,6 +15,8 @@ app.whenReady().then(() => {
protocol.registerFileProtocol('atom', (request, callback) => {
const url = request.url.substr(7)
callback({ path: path.normalize(`${__dirname}/${url}`) })
}, (error) => {
if (error) console.error('Failed to register protocol')
})
})
```
@@ -24,15 +26,9 @@ of the `app` module gets emitted.
## Using `protocol` with a custom `partition` or `session`
A protocol is registered to a specific Electron [`session`](./session.md)
object. If you don't specify a session, then your `protocol` will be applied to
the default session that Electron uses. However, if you define a `partition` or
`session` on your `browserWindow`'s `webPreferences`, then that window will use
a different session and your custom protocol will not work if you just use
`electron.protocol.XXX`.
A protocol is registered to a specific Electron [`session`](./session.md) object. If you don't specify a session, then your `protocol` will be applied to the default session that Electron uses. However, if you define a `partition` or `session` on your `browserWindow`'s `webPreferences`, then that window will use a different session and your custom protocol will not work if you just use `electron.protocol.XXX`.
To have your custom protocol work in combination with a custom session, you need
to register it to that session explicitly.
To have your custom protocol work in combination with a custom session, you need to register it to that session explicitly.
```javascript
const { session, app, protocol } = require('electron')
@@ -45,9 +41,17 @@ app.whenReady().then(() => {
ses.protocol.registerFileProtocol('atom', (request, callback) => {
const url = request.url.substr(7)
callback({ path: path.normalize(`${__dirname}/${url}`) })
}, (error) => {
if (error) console.error('Failed to register protocol')
})
mainWindow = new BrowserWindow({ webPreferences: { partition } })
mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
partition: partition
}
})
})
```
@@ -59,15 +63,15 @@ The `protocol` module has the following methods:
* `customSchemes` [CustomScheme[]](structures/custom-scheme.md)
**Note:** This method can only be used before the `ready` event of the `app`
module gets emitted and can be called only once.
Registers the `scheme` as standard, secure, bypasses content security policy for
resources, allows registering ServiceWorker, supports fetch API, and streaming
video/audio. Specify a privilege with the value of `true` to enable the capability.
Registers the `scheme` as standard, secure, bypasses content security policy for resources,
allows registering ServiceWorker and supports fetch API.
An example of registering a privileged scheme, that bypasses Content Security
Policy:
Specify a privilege with the value of `true` to enable the capability.
An example of registering a privileged scheme, with bypassing Content Security Policy:
```javascript
const { protocol } = require('electron')
@@ -80,7 +84,7 @@ A standard scheme adheres to what RFC 3986 calls [generic URI
syntax](https://tools.ietf.org/html/rfc3986#section-3). For example `http` and
`https` are standard schemes, while `file` is not.
Registering a scheme as standard allows relative and absolute resources to
Registering a scheme as standard, will allow relative and absolute resources to
be resolved correctly when served. Otherwise the scheme will behave like the
`file` protocol, but without the ability to resolve relative URLs.
@@ -98,107 +102,168 @@ Registering a scheme as standard will allow access to files through the
[FileSystem API][file-system-api]. Otherwise the renderer will throw a security
error for the scheme.
By default web storage apis (localStorage, sessionStorage, webSQL, indexedDB,
cookies) are disabled for non standard schemes. So in general if you want to
register a custom protocol to replace the `http` protocol, you have to register
it as a standard scheme.
By default web storage apis (localStorage, sessionStorage, webSQL, indexedDB, cookies)
are disabled for non standard schemes. So in general if you want to register a
custom protocol to replace the `http` protocol, you have to register it as a standard scheme.
Protocols that use streams (http and stream protocols) should set `stream: true`.
The `<video>` and `<audio>` HTML elements expect protocols to buffer their
responses by default. The `stream` flag configures those elements to correctly
expect streaming responses.
`protocol.registerSchemesAsPrivileged` can be used to replicate the functionality of the previous `protocol.registerStandardSchemes`, `webFrame.registerURLSchemeAs*` and `protocol.registerServiceWorkerSchemes` functions that existed prior to Electron 5.0.0, for example:
### `protocol.registerFileProtocol(scheme, handler)`
**before (<= v4.x)**
```javascript
// Main
protocol.registerStandardSchemes(['scheme1', 'scheme2'], { secure: true })
// Renderer
webFrame.registerURLSchemeAsPrivileged('scheme1', { secure: true })
webFrame.registerURLSchemeAsPrivileged('scheme2', { secure: true })
```
**after (>= v5.x)**
```javascript
protocol.registerSchemesAsPrivileged([
{ scheme: 'scheme1', privileges: { standard: true, secure: true } },
{ scheme: 'scheme2', privileges: { standard: true, secure: true } }
])
```
### `protocol.registerFileProtocol(scheme, handler[, completion])`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `request` Object
* `url` String
* `headers` Record<String, String>
* `referrer` String
* `method` String
* `uploadData` [UploadData[]](structures/upload-data.md)
* `callback` Function
* `response` (String | [ProtocolResponse](structures/protocol-response.md))
* `filePath` String | [FilePathWithHeaders](structures/file-path-with-headers.md) (optional)
* `completion` Function (optional)
* `error` Error
Returns `Boolean` - Whether the protocol was successfully registered
Registers a protocol of `scheme` that will send a file as the response. The
`handler` will be called with `request` and `callback` where `request` is
an incoming request for the `scheme`.
Registers a protocol of `scheme` that will send the file as a response. The
`handler` will be called with `handler(request, callback)` when a `request` is
going to be created with `scheme`. `completion` will be called with
`completion(null)` when `scheme` is successfully registered or
`completion(error)` when failed.
To handle the `request`, the `callback` should be called with either the file's
path or an object that has a `path` property, e.g. `callback(filePath)` or
`callback({ path: filePath })`. The `filePath` must be an absolute path.
`callback({ path: filePath })`. The object may also have a `headers` property
which gives a map of headers to values for the response headers, e.g.
`callback({ path: filePath, headers: {"Content-Security-Policy": "default-src 'none'"]})`.
When `callback` is called with nothing, a number, or an object that has an
`error` property, the `request` will fail with the `error` number you
specified. For the available error numbers you can use, please see the
[net error list][net-error].
By default the `scheme` is treated like `http:`, which is parsed differently
from protocols that follow the "generic URI syntax" like `file:`.
than protocols that follow the "generic URI syntax" like `file:`.
### `protocol.registerBufferProtocol(scheme, handler)`
### `protocol.registerBufferProtocol(scheme, handler[, completion])`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `request` Object
* `url` String
* `headers` Record<String, String>
* `referrer` String
* `method` String
* `uploadData` [UploadData[]](structures/upload-data.md)
* `callback` Function
* `response` (Buffer | [ProtocolResponse](structures/protocol-response.md))
Returns `Boolean` - Whether the protocol was successfully registered
* `buffer` (Buffer | [MimeTypedBuffer](structures/mime-typed-buffer.md)) (optional)
* `completion` Function (optional)
* `error` Error
Registers a protocol of `scheme` that will send a `Buffer` as a response.
The usage is the same with `registerFileProtocol`, except that the `callback`
should be called with either a `Buffer` object or an object that has the `data`
property.
should be called with either a `Buffer` object or an object that has the `data`,
`mimeType`, and `charset` properties.
Example:
```javascript
const { protocol } = require('electron')
protocol.registerBufferProtocol('atom', (request, callback) => {
callback({ mimeType: 'text/html', data: Buffer.from('<h5>Response</h5>') })
}, (error) => {
if (error) console.error('Failed to register protocol')
})
```
### `protocol.registerStringProtocol(scheme, handler)`
### `protocol.registerStringProtocol(scheme, handler[, completion])`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `request` Object
* `url` String
* `headers` Record<String, String>
* `referrer` String
* `method` String
* `uploadData` [UploadData[]](structures/upload-data.md)
* `callback` Function
* `response` (String | [ProtocolResponse](structures/protocol-response.md))
Returns `Boolean` - Whether the protocol was successfully registered
* `data` (String | [StringProtocolResponse](structures/string-protocol-response.md)) (optional)
* `completion` Function (optional)
* `error` Error
Registers a protocol of `scheme` that will send a `String` as a response.
The usage is the same with `registerFileProtocol`, except that the `callback`
should be called with either a `String` or an object that has the `data`
property.
should be called with either a `String` or an object that has the `data`,
`mimeType`, and `charset` properties.
### `protocol.registerHttpProtocol(scheme, handler)`
### `protocol.registerHttpProtocol(scheme, handler[, completion])`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `request` Object
* `url` String
* `headers` Record<String, String>
* `referrer` String
* `method` String
* `uploadData` [UploadData[]](structures/upload-data.md)
* `callback` Function
* `response` ProtocolResponse
Returns `Boolean` - Whether the protocol was successfully registered
* `redirectRequest` Object
* `url` String
* `method` String (optional)
* `session` Session | null (optional)
* `uploadData` [ProtocolResponseUploadData](structures/protocol-response-upload-data.md) (optional)
* `completion` Function (optional)
* `error` Error
Registers a protocol of `scheme` that will send an HTTP request as a response.
The usage is the same with `registerFileProtocol`, except that the `callback`
should be called with an object that has the `url` property.
should be called with a `redirectRequest` object that has the `url`, `method`,
`referrer`, `uploadData` and `session` properties.
### `protocol.registerStreamProtocol(scheme, handler)`
By default the HTTP request will reuse the current session. If you want the
request to have a different session you should set `session` to `null`.
For POST requests the `uploadData` object must be provided.
### `protocol.registerStreamProtocol(scheme, handler[, completion])`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `request` Object
* `url` String
* `headers` Record<String, String>
* `referrer` String
* `method` String
* `uploadData` [UploadData[]](structures/upload-data.md)
* `callback` Function
* `response` (ReadableStream | [ProtocolResponse](structures/protocol-response.md))
* `stream` (ReadableStream | [StreamProtocolResponse](structures/stream-protocol-response.md)) (optional)
* `completion` Function (optional)
* `error` Error
Returns `Boolean` - Whether the protocol was successfully registered
Registers a protocol of `scheme` that will send a `Readable` as a response.
Registers a protocol of `scheme` that will send a stream as a response.
The usage is the same with `registerFileProtocol`, except that the
`callback` should be called with either a [`ReadableStream`](https://nodejs.org/api/stream.html#stream_class_stream_readable) object or an object that
has the `data` property.
The usage is similar to the other `register{Any}Protocol`, except that the
`callback` should be called with either a `Readable` object or an object that
has the `data`, `statusCode`, and `headers` properties.
Example:
@@ -221,6 +286,8 @@ protocol.registerStreamProtocol('atom', (request, callback) => {
},
data: createStream('<h5>Response</h5>')
})
}, (error) => {
if (error) console.error('Failed to register protocol')
})
```
@@ -228,102 +295,132 @@ It is possible to pass any object that implements the readable stream API (emits
`data`/`end`/`error` events). For example, here's how a file could be returned:
```javascript
const { protocol } = require('electron')
const fs = require('fs')
protocol.registerStreamProtocol('atom', (request, callback) => {
callback(fs.createReadStream('index.html'))
}, (error) => {
if (error) console.error('Failed to register protocol')
})
```
### `protocol.unregisterProtocol(scheme)`
### `protocol.unregisterProtocol(scheme[, completion])`
* `scheme` String
Returns `Boolean` - Whether the protocol was successfully unregistered
* `completion` Function (optional)
* `error` Error
Unregisters the custom protocol of `scheme`.
### `protocol.isProtocolRegistered(scheme)`
### `protocol.isProtocolHandled(scheme)`
* `scheme` String
Returns `Boolean` - Whether `scheme` is already registered.
Returns `Promise<Boolean>` - fulfilled with a boolean that indicates whether there is
already a handler for `scheme`.
### `protocol.interceptFileProtocol(scheme, handler)`
### `protocol.interceptFileProtocol(scheme, handler[, completion])`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `request` Object
* `url` String
* `headers` Record<String, String>
* `referrer` String
* `method` String
* `uploadData` [UploadData[]](structures/upload-data.md)
* `callback` Function
* `response` (String | [ProtocolResponse](structures/protocol-response.md))
Returns `Boolean` - Whether the protocol was successfully intercepted
* `filePath` String
* `completion` Function (optional)
* `error` Error
Intercepts `scheme` protocol and uses `handler` as the protocol's new handler
which sends a file as a response.
### `protocol.interceptStringProtocol(scheme, handler)`
### `protocol.interceptStringProtocol(scheme, handler[, completion])`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `request` Object
* `url` String
* `headers` Record<String, String>
* `referrer` String
* `method` String
* `uploadData` [UploadData[]](structures/upload-data.md)
* `callback` Function
* `response` (String | [ProtocolResponse](structures/protocol-response.md))
Returns `Boolean` - Whether the protocol was successfully intercepted
* `data` (String | [StringProtocolResponse](structures/string-protocol-response.md)) (optional)
* `completion` Function (optional)
* `error` Error
Intercepts `scheme` protocol and uses `handler` as the protocol's new handler
which sends a `String` as a response.
### `protocol.interceptBufferProtocol(scheme, handler)`
### `protocol.interceptBufferProtocol(scheme, handler[, completion])`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `request` Object
* `url` String
* `headers` Record<String, String>
* `referrer` String
* `method` String
* `uploadData` [UploadData[]](structures/upload-data.md)
* `callback` Function
* `response` (Buffer | [ProtocolResponse](structures/protocol-response.md))
Returns `Boolean` - Whether the protocol was successfully intercepted
* `buffer` Buffer (optional)
* `completion` Function (optional)
* `error` Error
Intercepts `scheme` protocol and uses `handler` as the protocol's new handler
which sends a `Buffer` as a response.
### `protocol.interceptHttpProtocol(scheme, handler)`
### `protocol.interceptHttpProtocol(scheme, handler[, completion])`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `request` Object
* `url` String
* `headers` Record<String, String>
* `referrer` String
* `method` String
* `uploadData` [UploadData[]](structures/upload-data.md)
* `callback` Function
* `response` [ProtocolResponse](structures/protocol-response.md)
Returns `Boolean` - Whether the protocol was successfully intercepted
* `redirectRequest` Object
* `url` String
* `method` String (optional)
* `session` Session | null (optional)
* `uploadData` [ProtocolResponseUploadData](structures/protocol-response-upload-data.md) (optional)
* `completion` Function (optional)
* `error` Error
Intercepts `scheme` protocol and uses `handler` as the protocol's new handler
which sends a new HTTP request as a response.
### `protocol.interceptStreamProtocol(scheme, handler)`
### `protocol.interceptStreamProtocol(scheme, handler[, completion])`
* `scheme` String
* `handler` Function
* `request` ProtocolRequest
* `request` Object
* `url` String
* `headers` Record<String, String>
* `referrer` String
* `method` String
* `uploadData` [UploadData[]](structures/upload-data.md)
* `callback` Function
* `response` (ReadableStream | [ProtocolResponse](structures/protocol-response.md))
Returns `Boolean` - Whether the protocol was successfully intercepted
* `stream` (ReadableStream | [StreamProtocolResponse](structures/stream-protocol-response.md)) (optional)
* `completion` Function (optional)
* `error` Error
Same as `protocol.registerStreamProtocol`, except that it replaces an existing
protocol handler.
### `protocol.uninterceptProtocol(scheme)`
### `protocol.uninterceptProtocol(scheme[, completion])`
* `scheme` String
Returns `Boolean` - Whether the protocol was successfully unintercepted
* `completion` Function (optional)
* `error` Error
Remove the interceptor installed for `scheme` and restore its original handler.
### `protocol.isProtocolIntercepted(scheme)`
* `scheme` String
Returns `Boolean` - Whether `scheme` is already intercepted.
[net-error]: https://code.google.com/p/chromium/codesearch#chromium/src/net/base/net_error_list.h
[file-system-api]: https://developer.mozilla.org/en-US/docs/Web/API/LocalFileSystem

View File

@@ -39,7 +39,7 @@ Returns:
* `message` String - The actual console message
* `versionId` Number - The version ID of the service worker that sent the log message
* `source` String - The type of source for this message. Can be `javascript`, `xml`, `network`, `console-api`, `storage`, `app-cache`, `rendering`, `security`, `deprecation`, `worker`, `violation`, `intervention`, `recommendation` or `other`.
* `level` Number - The log level, from 0 to 3. In order it matches `verbose`, `info`, `warning` and `error`.
* `level` Number - The log level, from 0 to 3. In order it matches `verbose`, `info`, `warning` and `error`.
* `sourceUrl` String - The URL the message came from
* `lineNumber` Number - The line number of the source that triggered this console message

View File

@@ -8,4 +8,3 @@
* `allowServiceWorkers` Boolean (optional) - Default false.
* `supportFetchAPI` Boolean (optional) - Default false.
* `corsEnabled` Boolean (optional) - Default false.
* `stream` Boolean (optional) - Default false.

View File

@@ -11,8 +11,6 @@
* `Pepper Plugin`
* `Pepper Plugin Broker`
* `Unknown`
* `name` String (optional) - The name of the process. i.e. for plugins it might be Flash.
Examples for utility: `Audio Service`, `Content Decryption Module Service`, `Network Service`, `Video Capture`, etc.
* `cpu` [CPUUsage](cpu-usage.md) - CPU usage of the process.
* `creationTime` Number - Creation time for this process.
The time is represented as number of milliseconds since epoch.

View File

@@ -4,4 +4,3 @@
* `referrer` String
* `method` String
* `uploadData` [UploadData[]](upload-data.md) (optional)
* `headers` Record<String, String>

View File

@@ -6,7 +6,7 @@
* `trace_buffer_size_in_events` number (optional) - maximum size of the trace
recording buffer in events.
* `enable_argument_filter` boolean (optional) - if true, filter event data
according to a specific list of events that have been manually vetted to not
according to a whitelist of events that have been manually vetted to not
include any PII. See [the implementation in
Chromium][trace_event_args_whitelist.cc] for specifics.
* `included_categories` String[] (optional) - a list of tracing categories to

View File

@@ -416,7 +416,7 @@ This API itself will not protect your user data; rather, it is a mechanism to al
Returns `Boolean` - `true` if the current process is a trusted accessibility client and `false` if it is not.
### `systemPreferences.getMediaAccessStatus(mediaType)` _Windows_ _macOS_
### `systemPreferences.getMediaAccessStatus(mediaType)` _macOS_
* `mediaType` String - Can be `microphone`, `camera` or `screen`.
@@ -426,9 +426,6 @@ This user consent was not required on macOS 10.13 High Sierra or lower so this m
macOS 10.14 Mojave or higher requires consent for `microphone` and `camera` access.
macOS 10.15 Catalina or higher requires consent for `screen` access.
Windows 10 has a global setting controlling `microphone` and `camera` access for all win32 applications.
It will always return `granted` for `screen` and for all media types on older versions of Windows.
### `systemPreferences.askForMediaAccess(mediaType)` _macOS_
* `mediaType` String - the type of media being requested; can be `microphone`, `camera`.

View File

@@ -402,7 +402,6 @@ Returns:
* `key` String - Equivalent to [KeyboardEvent.key][keyboardevent].
* `code` String - Equivalent to [KeyboardEvent.code][keyboardevent].
* `isAutoRepeat` Boolean - Equivalent to [KeyboardEvent.repeat][keyboardevent].
* `isComposing` Boolean - Equivalent to [KeyboardEvent.isComposing][keyboardevent].
* `shift` Boolean - Equivalent to [KeyboardEvent.shiftKey][keyboardevent].
* `control` Boolean - Equivalent to [KeyboardEvent.controlKey][keyboardevent].
* `alt` Boolean - Equivalent to [KeyboardEvent.altKey][keyboardevent].
@@ -738,9 +737,9 @@ Emitted when a `<webview>` has been attached to this web contents.
Returns:
* `event` Event
* `level` Integer - The log level, from 0 to 3. In order it matches `verbose`, `info`, `warning` and `error`.
* `message` String - The actual console message
* `line` Integer - The line number of the source that triggered this console message
* `level` Integer
* `message` String
* `line` Integer
* `sourceId` String
Emitted when the associated window logs a console message.

View File

@@ -755,9 +755,9 @@ Fired when page leaves fullscreen triggered by HTML API.
Returns:
* `level` Integer - The log level, from 0 to 3. In order it matches `verbose`, `info`, `warning` and `error`.
* `message` String - The actual console message
* `line` Integer - The line number of the source that triggered this console message
* `level` Integer
* `message` String
* `line` Integer
* `sourceId` String
Fired when the guest window logs a console message.

View File

@@ -14,15 +14,6 @@ This document uses the following convention to categorize breaking changes:
## Planned Breaking API Changes (12.0)
### Default Changed: `contextIsolation` defaults to `true`
In Electron 12, `contextIsolation` will be enabled by default. To restore
the previous behavior, `contextIsolation: false` must be specified in WebPreferences.
We [recommend having contextIsolation enabled](https://github.com/electron/electron/blob/master/docs/tutorial/security.md#3-enable-context-isolation-for-remote-content) for the security of your application.
For more details see: https://github.com/electron/electron/issues/23506
### Removed: `crashReporter` methods in the renderer process
The following `crashReporter` methods are no longer available in the renderer

View File

@@ -23,3 +23,4 @@ For guides on Electron app development, see
* [Debugging on macOS](debugging-instructions-macos.md)
* [Setting Up Symbol Server in Debugger](setting-up-symbol-server.md)
* [Patches](patches.md)
* [Upgrading Node](upgrading-node.md)

View File

@@ -10,10 +10,6 @@ Check the build prerequisites for your platform before proceeding
* [Linux](build-instructions-linux.md#prerequisites)
* [Windows](build-instructions-windows.md#prerequisites)
## Build Tools
[Electron's Build Tools](https://github.com/electron/build-tools) automate much of the setup for compiling Electron from source with different configurations and build targets. If you wish to set up the environment manually, the instructions are listed below.
## GN prerequisites
You'll need to install [`depot_tools`][depot-tools], the toolset

View File

@@ -0,0 +1,121 @@
# Upgrading Node
## Discussion
Chromium and Node.js both depend on V8, and Electron contains
only a single copy of V8, so it's important to ensure that the
version of V8 chosen is compatible with the build's version of
Node.js and Chromium.
Upgrading Node is much easier than upgrading Chromium, so fewer
conflicts arise if one upgrades Chromium first, then chooses the
upstream Node release whose version of V8 is closest to the one
Chromium contains.
Electron has its own [Node fork](https://github.com/electron/node)
with modifications for the V8 build details mentioned above
and for exposing API needed by Electron. Once an upstream Node
release is chosen, it's placed in a branch in Electron's Node fork
and any Electron Node patches are applied there.
Another factor is that the Node project patches its version of V8.
As mentioned above, Electron builds everything with a single copy
of V8, so Node's V8 patches must be ported to that copy.
Once all of Electron's dependencies are building and using the same
copy of V8, the next step is to fix any Electron code issues caused
by the Node upgrade.
[FIXME] something about a Node debugger in Atom that we (e.g. deepak)
use and need to confirm doesn't break with the Node upgrade?
So in short, the primary steps are:
1. Update Electron's Node fork to the desired version
2. Backport Node's V8 patches to our copy of V8
3. Update the GN build files, porting changes from node's GYP files
4. Update Electron's DEPS to use new version of Node
## Updating Electron's Node [fork](https://github.com/electron/node)
1. Ensure that `master` on `electron/node` has updated release tags from `nodejs/node`
2. Create a branch in https://github.com/electron/node: `electron-node-vX.X.X` where the base that you're branching from is the tag for the desired update
- `vX.X.X` Must use a version of Node compatible with our current version of Chromium
3. Re-apply our commits from the previous version of Node we were using (`vY.Y.Y`) to `v.X.X.X`
- Check release tag and select the range of commits we need to re-apply
- Cherry-pick commit range:
1. Checkout both `vY.Y.Y` & `v.X.X.X`
2. `git cherry-pick FIRST_COMMIT_HASH..LAST_COMMIT_HASH`
- Resolve merge conflicts in each file encountered, then:
1. `git add <conflict-file>`
2. `git cherry-pick --continue`
3. Repeat until finished
## Updating [V8](https://github.com/electron/node/src/V8) Patches
We need to generate a patch file from each patch that Node
applies to V8.
```sh
$ cd third_party/electron_node
$ CURRENT_NODE_VERSION=vX.Y.Z
# Find the last commit with the message "deps: update V8 to <some version>"
# This commit corresponds to Node resetting V8 to its pristine upstream
# state at the stated version.
$ LAST_V8_UPDATE="$(git log --grep='^deps: update V8' --format='%H' -1 deps/v8)"
# This creates a patch file containing all changes in deps/v8 from
# $LAST_V8_UPDATE up to the current Node version, formatted in a way that
# it will apply cleanly to the V8 repository (i.e. with `deps/v8`
# stripped off the path and excluding the v8/gypfiles directory, which
# isn't present in V8.
$ git format-patch \
--relative=deps/v8 \
$LAST_V8_UPDATE..$CURRENT_NODE_VERSION \
deps/v8 \
':(exclude)deps/v8/gypfiles' \
--stdout \
> ../../electron/common/patches/v8/node_v8_patches.patch
```
This list of patches will probably include one that claims to
make the V8 API backwards-compatible with a previous version of
V8. Unfortunately, those patches almost always change the V8 API
in a way that is incompatible with Chromium.
It's usually easier to update Node to work without the
compatibility patch than to update Chromium to work with the
compatibility patch, so it's recommended to revert the
compatibility patch and fix any errors that arise when compiling
Node.
## Update Electron's `DEPS` file
Update the `DEPS` file in the root of
[electron/electron](https://github.com/electron/electron) to
point to the git hash of the updated Node.
## Notes
- Node maintains its own fork of V8
- They backport a small amount of things as needed
- Documentation in Node about how [they work with V8](https://nodejs.org/api/v8.html)
- We update code such that we only use one copy of V8 across all of Electron
- E.g Electron, Chromium, and Node.js
- We dont track upstream closely due to logistics:
- Upstream uses multiple repos and so merging into a single repo
would result in lost history. So we only update when were planning
a Node version bump in Electron.
- Chromium is large and time-consuming to update, so we typically
choose the Node version based on which of its releases has a version
of V8 thats closest to the version in Chromium that were using.
- We sometimes have to wait for the next periodic Node release
because it will sync more closely with the version of V8 in the new Chromium
- Electron keeps all its patches in the repo because its simpler than
maintaining different repos for patches for each upstream project.
- Crashpad, Node.js, Chromium, Skia etc. patches are all kept in the same place
- Building Node:
- We maintain our own GN build files for Node.js to make it easier to ensure
that eevrything is built with the same compiler flags.
This means that every time we upgrade Node.js we have to do a modest amount of
work to synchronize the GN files with the upstream GYP files.

View File

@@ -0,0 +1,3 @@
# Electron App Feedback Program
Electron is working on building a streamlined release process and having faster releases. To help with that, we have the App Feedback Program for large-scale Electron apps to test our beta releases and report app-specific issues to the Electron team. We use this program to help us prioritize work and get applications upgraded to the next stable release as soon as possible. There are a few requirements we expect from participants, such as attending short, online weekly check-ins. Please visit the [blog post](https://electronjs.org/blog/app-feedback-program) for details and sign-up.

View File

@@ -100,25 +100,6 @@ To see all of this in action, check out Electron Fiddle's source code,
[especially its `electron-forge` configuration
file](https://github.com/electron/fiddle/blob/master/forge.config.js).
If you plan to access the microphone or camera within your app using Electron's APIs, you'll also
need to add the following entitlements:
```xml
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
```
If these are not present in your app's entitlements when you invoke, for example:
```js
const { systemPreferences } = require('electron')
const microphone = systemPreferences.askForMediaAccess('microphone')
```
Your app may crash. See the Resource Access section in [Hardened Runtime](https://developer.apple.com/documentation/security/hardened_runtime) for more information and entitlements you may need.
## `electron-builder`

View File

@@ -87,7 +87,7 @@ e.g. `2.0.1`.
Specifically, the above means:
1. Admitting non-breaking-API changes before Week 3 in the beta cycle is okay, even if those changes have the potential to cause moderate side-effects
1. Admitting non-breaking-API changes before Week 3 in the beta cycle is okay, even if those changes have the potential to cause moderate side-affects
2. Admitting feature-flagged changes, that do not otherwise alter existing code paths, at most points in the beta cycle is okay. Users can explicitly enable those flags in their apps.
3. Admitting features of any sort after Week 3 in the beta cycle is 👎 without a very good reason.
@@ -139,7 +139,7 @@ We seek to increase clarity at all levels of the update and releases process. St
* Commits that would result in a semver **minor** bump must start with `feat:`.
* Commits that would result in a semver **patch** bump must start with `fix:`.
* We allow squashing of commits, provided that the squashed message adheres to the above message format.
* We allow squashing of commits, provided that the squashed message adheres the the above message format.
* It is acceptable for some commits in a pull request to not include a semantic prefix, as long as the pull request title contains a meaningful encompassing semantic message.
# Versioned `master`

View File

@@ -44,10 +44,23 @@ run the main process. An example of your `package.json` might look like this:
```
__Note__: If the `main` field is not present in `package.json`, Electron will
attempt to load an `index.js` (as Node.js does).
attempt to load an `index.js` (as Node.js does). If this was actually
a simple Node application, you would add a `start` script that instructs `node`
to execute the current package:
By default, `npm start` would run the main script with Node.js. in order to make
it run with Electron, you can add a `start` script:
```json
{
"name": "your-app",
"version": "0.1.0",
"main": "main.js",
"scripts": {
"start": "node ."
}
}
```
Turning this Node application into an Electron application is quite simple - we
merely replace the `node` runtime with the `electron` runtime.
```json
{
@@ -141,10 +154,10 @@ function createWindow () {
// Some APIs can only be used after this event occurs.
app.whenReady().then(createWindow)
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}

View File

@@ -55,9 +55,7 @@ template("electron_extra_paks") {
output = "${invoker.output_dir}/resources.pak"
sources = [
"$root_gen_dir/components/components_resources.pak",
"$root_gen_dir/content/browser/resources/media/media_internals_resources.pak",
"$root_gen_dir/content/browser/tracing/tracing_resources.pak",
"$root_gen_dir/content/browser/webrtc/resources/webrtc_internals_resources.pak",
"$root_gen_dir/content/content_resources.pak",
"$root_gen_dir/content/dev_ui_content_resources.pak",
"$root_gen_dir/mojo/public/js/mojo_bindings_resources.pak",
@@ -70,9 +68,7 @@ template("electron_extra_paks") {
"//components/resources",
"//content:content_resources",
"//content:dev_ui_content_resources",
"//content/browser/resources/media:media_internals_resources",
"//content/browser/tracing:resources",
"//content/browser/webrtc/resources",
"//electron:resources",
"//mojo/public/js:resources",
"//net:net_resources",

View File

@@ -43,6 +43,7 @@ auto_filenames = {
"docs/api/power-monitor.md",
"docs/api/power-save-blocker.md",
"docs/api/process.md",
"docs/api/protocol-ns.md",
"docs/api/protocol.md",
"docs/api/remote.md",
"docs/api/sandbox-option.md",
@@ -135,12 +136,13 @@ auto_filenames = {
sandbox_bundle_deps = [
"lib/browser/api/module-names.ts",
"lib/common/api/clipboard.ts",
"lib/common/api/clipboard.js",
"lib/common/api/deprecate.ts",
"lib/common/api/module-list.ts",
"lib/common/api/native-image.ts",
"lib/common/api/shell.ts",
"lib/common/api/native-image.js",
"lib/common/api/shell.js",
"lib/common/define-properties.ts",
"lib/common/electron-binding-setup.ts",
"lib/common/type-utils.ts",
"lib/common/web-view-methods.ts",
"lib/common/webpack-globals-provider.ts",
@@ -148,7 +150,7 @@ auto_filenames = {
"lib/renderer/api/crash-reporter.ts",
"lib/renderer/api/desktop-capturer.ts",
"lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/remote.ts",
"lib/renderer/api/remote.js",
"lib/renderer/api/web-frame.ts",
"lib/renderer/inspector.ts",
"lib/renderer/ipc-renderer-internal-utils.ts",
@@ -165,65 +167,62 @@ auto_filenames = {
"lib/renderer/window-setup.ts",
"lib/sandboxed_renderer/api/exports/electron.ts",
"lib/sandboxed_renderer/api/module-list.ts",
"lib/sandboxed_renderer/init.ts",
"lib/sandboxed_renderer/init.js",
"package.json",
"tsconfig.electron.json",
"tsconfig.json",
"typings/internal-ambient.d.ts",
"typings/internal-electron.d.ts",
]
isolated_bundle_deps = [
"lib/common/electron-binding-setup.ts",
"lib/isolated_renderer/init.js",
"lib/renderer/web-view/web-view-constants.ts",
"lib/renderer/web-view/web-view-element.ts",
"package.json",
"tsconfig.electron.json",
"tsconfig.json",
"typings/internal-ambient.d.ts",
"typings/internal-electron.d.ts",
]
browser_bundle_deps = [
"lib/browser/api/app.ts",
"lib/browser/api/auto-updater.ts",
"lib/browser/api/auto-updater/auto-updater-native.ts",
"lib/browser/api/auto-updater/auto-updater-win.ts",
"lib/browser/api/auto-updater/squirrel-update-win.ts",
"lib/browser/api/base-window.ts",
"lib/browser/api/browser-view.ts",
"lib/browser/api/browser-window.ts",
"lib/browser/api/content-tracing.ts",
"lib/browser/api/auto-updater.js",
"lib/browser/api/auto-updater/auto-updater-native.js",
"lib/browser/api/auto-updater/auto-updater-win.js",
"lib/browser/api/auto-updater/squirrel-update-win.js",
"lib/browser/api/browser-view.js",
"lib/browser/api/browser-window.js",
"lib/browser/api/content-tracing.js",
"lib/browser/api/crash-reporter.ts",
"lib/browser/api/desktop-capturer.ts",
"lib/browser/api/dialog.ts",
"lib/browser/api/dialog.js",
"lib/browser/api/exports/electron.ts",
"lib/browser/api/global-shortcut.ts",
"lib/browser/api/in-app-purchase.ts",
"lib/browser/api/global-shortcut.js",
"lib/browser/api/in-app-purchase.js",
"lib/browser/api/ipc-main.ts",
"lib/browser/api/menu-item-roles.ts",
"lib/browser/api/menu-item.ts",
"lib/browser/api/menu-utils.ts",
"lib/browser/api/menu.ts",
"lib/browser/api/menu-item-roles.js",
"lib/browser/api/menu-item.js",
"lib/browser/api/menu-utils.js",
"lib/browser/api/menu.js",
"lib/browser/api/message-channel.ts",
"lib/browser/api/module-list.ts",
"lib/browser/api/native-theme.ts",
"lib/browser/api/net-log.ts",
"lib/browser/api/net-log.js",
"lib/browser/api/net.ts",
"lib/browser/api/notification.ts",
"lib/browser/api/notification.js",
"lib/browser/api/power-monitor.ts",
"lib/browser/api/power-save-blocker.ts",
"lib/browser/api/power-save-blocker.js",
"lib/browser/api/protocol.ts",
"lib/browser/api/screen.ts",
"lib/browser/api/session.ts",
"lib/browser/api/session.js",
"lib/browser/api/system-preferences.ts",
"lib/browser/api/top-level-window.js",
"lib/browser/api/touch-bar.js",
"lib/browser/api/tray.ts",
"lib/browser/api/view.ts",
"lib/browser/api/views/image-view.ts",
"lib/browser/api/web-contents-view.ts",
"lib/browser/api/web-contents.ts",
"lib/browser/chrome-extension-shim.ts",
"lib/browser/api/tray.js",
"lib/browser/api/view.js",
"lib/browser/api/views/image-view.js",
"lib/browser/api/web-contents-view.js",
"lib/browser/api/web-contents.js",
"lib/browser/chrome-extension-shim.js",
"lib/browser/default-menu.ts",
"lib/browser/desktop-capturer.ts",
"lib/browser/devtools.ts",
@@ -234,17 +233,18 @@ auto_filenames = {
"lib/browser/ipc-main-internal-utils.ts",
"lib/browser/ipc-main-internal.ts",
"lib/browser/message-port-main.ts",
"lib/browser/navigation-controller.ts",
"lib/browser/navigation-controller.js",
"lib/browser/remote/objects-registry.ts",
"lib/browser/remote/server.ts",
"lib/browser/rpc-server.ts",
"lib/browser/rpc-server.js",
"lib/browser/utils.ts",
"lib/common/api/clipboard.ts",
"lib/common/api/clipboard.js",
"lib/common/api/deprecate.ts",
"lib/common/api/module-list.ts",
"lib/common/api/native-image.ts",
"lib/common/api/shell.ts",
"lib/common/api/native-image.js",
"lib/common/api/shell.js",
"lib/common/define-properties.ts",
"lib/common/electron-binding-setup.ts",
"lib/common/init.ts",
"lib/common/parse-features-string.ts",
"lib/common/reset-search-paths.ts",
@@ -256,18 +256,17 @@ auto_filenames = {
"package.json",
"tsconfig.electron.json",
"tsconfig.json",
"typings/internal-ambient.d.ts",
"typings/internal-electron.d.ts",
]
renderer_bundle_deps = [
"lib/browser/api/module-names.ts",
"lib/common/api/clipboard.ts",
"lib/common/api/clipboard.js",
"lib/common/api/deprecate.ts",
"lib/common/api/module-list.ts",
"lib/common/api/native-image.ts",
"lib/common/api/shell.ts",
"lib/common/api/native-image.js",
"lib/common/api/shell.js",
"lib/common/define-properties.ts",
"lib/common/electron-binding-setup.ts",
"lib/common/init.ts",
"lib/common/reset-search-paths.ts",
"lib/common/type-utils.ts",
@@ -279,7 +278,7 @@ auto_filenames = {
"lib/renderer/api/exports/electron.ts",
"lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/module-list.ts",
"lib/renderer/api/remote.ts",
"lib/renderer/api/remote.js",
"lib/renderer/api/web-frame.ts",
"lib/renderer/init.ts",
"lib/renderer/inspector.ts",
@@ -299,18 +298,17 @@ auto_filenames = {
"package.json",
"tsconfig.electron.json",
"tsconfig.json",
"typings/internal-ambient.d.ts",
"typings/internal-electron.d.ts",
]
worker_bundle_deps = [
"lib/browser/api/module-names.ts",
"lib/common/api/clipboard.ts",
"lib/common/api/clipboard.js",
"lib/common/api/deprecate.ts",
"lib/common/api/module-list.ts",
"lib/common/api/native-image.ts",
"lib/common/api/shell.ts",
"lib/common/api/native-image.js",
"lib/common/api/shell.js",
"lib/common/define-properties.ts",
"lib/common/electron-binding-setup.ts",
"lib/common/init.ts",
"lib/common/reset-search-paths.ts",
"lib/common/type-utils.ts",
@@ -321,7 +319,7 @@ auto_filenames = {
"lib/renderer/api/exports/electron.ts",
"lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/module-list.ts",
"lib/renderer/api/remote.ts",
"lib/renderer/api/remote.js",
"lib/renderer/api/web-frame.ts",
"lib/renderer/ipc-renderer-internal-utils.ts",
"lib/renderer/ipc-renderer-internal.ts",
@@ -331,7 +329,5 @@ auto_filenames = {
"package.json",
"tsconfig.electron.json",
"tsconfig.json",
"typings/internal-ambient.d.ts",
"typings/internal-electron.d.ts",
]
}

View File

@@ -44,8 +44,6 @@ filenames = {
"shell/browser/api/electron_api_app_mac.mm",
"shell/browser/api/electron_api_auto_updater.cc",
"shell/browser/api/electron_api_auto_updater.h",
"shell/browser/api/electron_api_base_window.cc",
"shell/browser/api/electron_api_base_window.h",
"shell/browser/api/electron_api_browser_view.cc",
"shell/browser/api/electron_api_browser_view.h",
"shell/browser/api/electron_api_browser_window.cc",
@@ -104,6 +102,8 @@ filenames = {
"shell/browser/api/electron_api_system_preferences.h",
"shell/browser/api/electron_api_system_preferences_mac.mm",
"shell/browser/api/electron_api_system_preferences_win.cc",
"shell/browser/api/electron_api_top_level_window.cc",
"shell/browser/api/electron_api_top_level_window.h",
"shell/browser/api/electron_api_tray.cc",
"shell/browser/api/electron_api_tray.h",
"shell/browser/api/electron_api_url_loader.cc",
@@ -525,8 +525,6 @@ filenames = {
"shell/common/gin_helper/function_template_extensions.h",
"shell/common/gin_helper/locker.cc",
"shell/common/gin_helper/locker.h",
"shell/common/gin_helper/microtasks_scope.cc",
"shell/common/gin_helper/microtasks_scope.h",
"shell/common/gin_helper/object_template_builder.cc",
"shell/common/gin_helper/object_template_builder.h",
"shell/common/gin_helper/persistent_dictionary.cc",

View File

@@ -4,8 +4,8 @@ import * as path from 'path';
import { deprecate, Menu } from 'electron';
import { EventEmitter } from 'events';
const bindings = process._linkedBinding('electron_browser_app');
const commandLine = process._linkedBinding('electron_common_command_line');
const bindings = process.electronBinding('app');
const commandLine = process.electronBinding('command_line');
const { app, App } = bindings;
// Only one app object permitted.

View File

@@ -0,0 +1,7 @@
'use strict';
if (process.platform === 'win32') {
module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-win');
} else {
module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-native');
}

View File

@@ -1,5 +0,0 @@
if (process.platform === 'win32') {
module.exports = require('./auto-updater/auto-updater-win');
} else {
module.exports = require('./auto-updater/auto-updater-native');
}

View File

@@ -0,0 +1,10 @@
'use strict';
const EventEmitter = require('events').EventEmitter;
const { autoUpdater, AutoUpdater } = process.electronBinding('auto_updater');
// AutoUpdater is an EventEmitter.
Object.setPrototypeOf(AutoUpdater.prototype, EventEmitter.prototype);
EventEmitter.call(autoUpdater);
module.exports = autoUpdater;

View File

@@ -1,8 +0,0 @@
import { EventEmitter } from 'events';
const { autoUpdater, AutoUpdater } = process._linkedBinding('electron_browser_auto_updater');
// AutoUpdater is an EventEmitter.
Object.setPrototypeOf(AutoUpdater.prototype, EventEmitter.prototype);
EventEmitter.call(autoUpdater);
export default autoUpdater;

View File

@@ -1,14 +1,13 @@
import { app } from 'electron';
import { EventEmitter } from 'events';
import * as squirrelUpdate from './squirrel-update-win';
'use strict';
const { app } = require('electron');
const { EventEmitter } = require('events');
const squirrelUpdate = require('@electron/internal/browser/api/auto-updater/squirrel-update-win');
class AutoUpdater extends EventEmitter {
updateAvailable: boolean = false;
updateURL: string | null = null;
quitAndInstall () {
if (!this.updateAvailable) {
return this.emitError(new Error('No update available, can\'t quit and install'));
return this.emitError('No update available, can\'t quit and install');
}
squirrelUpdate.processStart();
app.quit();
@@ -18,8 +17,8 @@ class AutoUpdater extends EventEmitter {
return this.updateURL;
}
setFeedURL (options: { url: string } | string) {
let updateURL: string;
setFeedURL (options) {
let updateURL;
if (typeof options === 'object') {
if (typeof options.url === 'string') {
updateURL = options.url;
@@ -35,15 +34,14 @@ class AutoUpdater extends EventEmitter {
}
checkForUpdates () {
const url = this.updateURL;
if (!url) {
return this.emitError(new Error('Update URL is not set'));
if (!this.updateURL) {
return this.emitError('Update URL is not set');
}
if (!squirrelUpdate.supported()) {
return this.emitError(new Error('Can not find Squirrel'));
return this.emitError('Can not find Squirrel');
}
this.emit('checking-for-update');
squirrelUpdate.checkForUpdate(url, (error, update) => {
squirrelUpdate.checkForUpdate(this.updateURL, (error, update) => {
if (error != null) {
return this.emitError(error);
}
@@ -52,7 +50,7 @@ class AutoUpdater extends EventEmitter {
}
this.updateAvailable = true;
this.emit('update-available');
squirrelUpdate.update(url, (error) => {
squirrelUpdate.update(this.updateURL, (error) => {
if (error != null) {
return this.emitError(error);
}
@@ -68,9 +66,9 @@ class AutoUpdater extends EventEmitter {
// Private: Emit both error object and message, this is to keep compatibility
// with Old APIs.
emitError (error: Error) {
this.emit('error', error, error.message);
emitError (message) {
this.emit('error', new Error(message), message);
}
}
export default new AutoUpdater();
module.exports = new AutoUpdater();

View File

@@ -1,6 +1,8 @@
import * as fs from 'fs';
import * as path from 'path';
import { spawn, ChildProcessWithoutNullStreams } from 'child_process';
'use strict';
const fs = require('fs');
const path = require('path');
const spawn = require('child_process').spawn;
// i.e. my-app/app-0.1.13/
const appFolder = path.dirname(process.execPath);
@@ -8,15 +10,15 @@ const appFolder = path.dirname(process.execPath);
// i.e. my-app/Update.exe
const updateExe = path.resolve(appFolder, '..', 'Update.exe');
const exeName = path.basename(process.execPath);
let spawnedArgs: string[] = [];
let spawnedProcess: ChildProcessWithoutNullStreams | undefined;
let spawnedArgs = [];
let spawnedProcess;
const isSameArgs = (args: string[]) => args.length === spawnedArgs.length && args.every((e, i) => e === spawnedArgs[i]);
const isSameArgs = (args) => args.length === spawnedArgs.length && args.every((e, i) => e === spawnedArgs[i]);
// Spawn a command and invoke the callback when it completes with an error
// and the output from standard out.
const spawnUpdate = function (args: string[], detached: boolean, callback: Function) {
let error: Error, errorEmitted: boolean, stderr: string, stdout: string;
const spawnUpdate = function (args, detached, callback) {
let error, errorEmitted, stderr, stdout;
try {
// Ensure we don't spawn multiple squirrel processes
@@ -77,13 +79,13 @@ const spawnUpdate = function (args: string[], detached: boolean, callback: Funct
};
// Start an instance of the installed app.
export function processStart () {
exports.processStart = function () {
return spawnUpdate(['--processStartAndWait', exeName], true, function () {});
}
};
// Download the releases specified by the URL and write new results to stdout.
export function checkForUpdate (updateURL: string, callback: (error: Error | null, update?: any) => void) {
return spawnUpdate(['--checkForUpdate', updateURL], false, function (error: Error, stdout: string) {
exports.checkForUpdate = function (updateURL, callback) {
return spawnUpdate(['--checkForUpdate', updateURL], false, function (error, stdout) {
let ref, ref1, update;
if (error != null) {
return callback(error);
@@ -91,27 +93,27 @@ export function checkForUpdate (updateURL: string, callback: (error: Error | nul
try {
// Last line of output is the JSON details about the releases
const json = stdout.trim().split('\n').pop();
update = (ref = JSON.parse(json!)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === 'function' ? ref1.pop() : undefined : undefined : undefined;
update = (ref = JSON.parse(json)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === 'function' ? ref1.pop() : undefined : undefined : undefined;
} catch {
// Disabled for backwards compatibility:
// eslint-disable-next-line standard/no-callback-literal
return callback(new Error(`Invalid result:\n${stdout}`));
return callback(`Invalid result:\n${stdout}`);
}
return callback(null, update);
});
}
};
// Update the application to the latest remote version specified by URL.
export function update (updateURL: string, callback: (error: Error) => void) {
exports.update = function (updateURL, callback) {
return spawnUpdate(['--update', updateURL], false, callback);
}
};
// Is the Update.exe installed with the current application?
export function supported () {
exports.supported = function () {
try {
fs.accessSync(updateExe, fs.constants.R_OK);
fs.accessSync(updateExe, fs.R_OK);
return true;
} catch {
return false;
}
}
};

View File

@@ -0,0 +1,16 @@
'use strict';
const { EventEmitter } = require('events');
const { BrowserView } = process.electronBinding('browser_view');
Object.setPrototypeOf(BrowserView.prototype, EventEmitter.prototype);
BrowserView.fromWebContents = (webContents) => {
for (const view of BrowserView.getAllViews()) {
if (view.webContents.equal(webContents)) return view;
}
return null;
};
module.exports = BrowserView;

View File

@@ -1,15 +0,0 @@
import { EventEmitter } from 'events';
const { BrowserView } = process._linkedBinding('electron_browser_browser_view');
Object.setPrototypeOf(BrowserView.prototype, EventEmitter.prototype);
BrowserView.fromWebContents = (webContents: Electron.WebContents) => {
for (const view of BrowserView.getAllViews()) {
if (view.webContents.equal(webContents)) return view;
}
return null;
};
export default BrowserView;

View File

@@ -0,0 +1,168 @@
'use strict';
const electron = require('electron');
const { TopLevelWindow, deprecate } = electron;
const { BrowserWindow } = process.electronBinding('window');
Object.setPrototypeOf(BrowserWindow.prototype, TopLevelWindow.prototype);
BrowserWindow.prototype._init = function () {
// Call parent class's _init.
TopLevelWindow.prototype._init.call(this);
// Avoid recursive require.
const { app } = electron;
const nativeSetBounds = this.setBounds;
this.setBounds = (bounds, ...opts) => {
bounds = {
...this.getBounds(),
...bounds
};
nativeSetBounds.call(this, bounds, ...opts);
};
// Sometimes the webContents doesn't get focus when window is shown, so we
// have to force focusing on webContents in this case. The safest way is to
// focus it when we first start to load URL, if we do it earlier it won't
// have effect, if we do it later we might move focus in the page.
//
// Though this hack is only needed on macOS when the app is launched from
// Finder, we still do it on all platforms in case of other bugs we don't
// know.
this.webContents.once('load-url', function () {
this.focus();
});
// Redirect focus/blur event to app instance too.
this.on('blur', (event) => {
app.emit('browser-window-blur', event, this);
});
this.on('focus', (event) => {
app.emit('browser-window-focus', event, this);
});
// Subscribe to visibilityState changes and pass to renderer process.
let isVisible = this.isVisible() && !this.isMinimized();
const visibilityChanged = () => {
const newState = this.isVisible() && !this.isMinimized();
if (isVisible !== newState) {
isVisible = newState;
const visibilityState = isVisible ? 'visible' : 'hidden';
this.webContents.emit('-window-visibility-change', visibilityState);
}
};
const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore'];
for (const event of visibilityEvents) {
this.on(event, visibilityChanged);
}
// Notify the creation of the window.
const event = process.electronBinding('event').createEmpty();
app.emit('browser-window-created', event, this);
Object.defineProperty(this, 'devToolsWebContents', {
enumerable: true,
configurable: false,
get () {
return this.webContents.devToolsWebContents;
}
});
};
const isBrowserWindow = (win) => {
return win && win.constructor.name === 'BrowserWindow';
};
BrowserWindow.fromId = (id) => {
const win = TopLevelWindow.fromId(id);
return isBrowserWindow(win) ? win : null;
};
BrowserWindow.getAllWindows = () => {
return TopLevelWindow.getAllWindows().filter(isBrowserWindow);
};
BrowserWindow.getFocusedWindow = () => {
for (const window of BrowserWindow.getAllWindows()) {
if (window.isFocused() || window.isDevToolsFocused()) return window;
}
return null;
};
BrowserWindow.fromWebContents = (webContents) => {
for (const window of BrowserWindow.getAllWindows()) {
if (window.webContents && window.webContents.equal(webContents)) return window;
}
return null;
};
BrowserWindow.fromBrowserView = (browserView) => {
for (const window of BrowserWindow.getAllWindows()) {
if (window.getBrowserView() === browserView) return window;
}
return null;
};
// Helpers.
Object.assign(BrowserWindow.prototype, {
loadURL (...args) {
return this.webContents.loadURL(...args);
},
getURL (...args) {
return this.webContents.getURL();
},
loadFile (...args) {
return this.webContents.loadFile(...args);
},
reload (...args) {
return this.webContents.reload(...args);
},
send (...args) {
return this.webContents.send(...args);
},
openDevTools (...args) {
return this.webContents.openDevTools(...args);
},
closeDevTools () {
return this.webContents.closeDevTools();
},
isDevToolsOpened () {
return this.webContents.isDevToolsOpened();
},
isDevToolsFocused () {
return this.webContents.isDevToolsFocused();
},
toggleDevTools () {
return this.webContents.toggleDevTools();
},
inspectElement (...args) {
return this.webContents.inspectElement(...args);
},
inspectSharedWorker () {
return this.webContents.inspectSharedWorker();
},
inspectServiceWorker () {
return this.webContents.inspectServiceWorker();
},
showDefinitionForSelection () {
return this.webContents.showDefinitionForSelection();
},
capturePage (...args) {
return this.webContents.capturePage(...args);
},
setTouchBar (touchBar) {
electron.TouchBar._setOnWindow(touchBar, this);
},
getBackgroundThrottling () {
return this.webContents.getBackgroundThrottling();
},
setBackgroundThrottling (allowed) {
this.webContents.setBackgroundThrottling(allowed);
}
});
module.exports = BrowserWindow;

View File

@@ -1,182 +0,0 @@
import { BaseWindow, WebContents, Event, BrowserView, TouchBar } from 'electron';
import type { BrowserWindow as BWT } from 'electron';
const { BrowserWindow } = process._linkedBinding('electron_browser_window') as { BrowserWindow: typeof BWT };
Object.setPrototypeOf(BrowserWindow.prototype, BaseWindow.prototype);
(BrowserWindow.prototype as any)._init = function (this: BWT) {
// Call parent class's _init.
(BaseWindow.prototype as any)._init.call(this);
// Avoid recursive require.
const { app } = require('electron');
const nativeSetBounds = this.setBounds;
this.setBounds = (bounds, ...opts) => {
bounds = {
...this.getBounds(),
...bounds
};
nativeSetBounds.call(this, bounds, ...opts);
};
// Sometimes the webContents doesn't get focus when window is shown, so we
// have to force focusing on webContents in this case. The safest way is to
// focus it when we first start to load URL, if we do it earlier it won't
// have effect, if we do it later we might move focus in the page.
//
// Though this hack is only needed on macOS when the app is launched from
// Finder, we still do it on all platforms in case of other bugs we don't
// know.
this.webContents.once('load-url' as any, function (this: WebContents) {
this.focus();
});
// Redirect focus/blur event to app instance too.
this.on('blur', (event: Event) => {
app.emit('browser-window-blur', event, this);
});
this.on('focus', (event: Event) => {
app.emit('browser-window-focus', event, this);
});
// Subscribe to visibilityState changes and pass to renderer process.
let isVisible = this.isVisible() && !this.isMinimized();
const visibilityChanged = () => {
const newState = this.isVisible() && !this.isMinimized();
if (isVisible !== newState) {
isVisible = newState;
const visibilityState = isVisible ? 'visible' : 'hidden';
this.webContents.emit('-window-visibility-change', visibilityState);
}
};
const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore'];
for (const event of visibilityEvents) {
this.on(event as any, visibilityChanged);
}
// Notify the creation of the window.
const event = process._linkedBinding('electron_browser_event').createEmpty();
app.emit('browser-window-created', event, this);
Object.defineProperty(this, 'devToolsWebContents', {
enumerable: true,
configurable: false,
get () {
return this.webContents.devToolsWebContents;
}
});
};
const isBrowserWindow = (win: any) => {
return win && win.constructor.name === 'BrowserWindow';
};
BrowserWindow.fromId = (id: number) => {
const win = BaseWindow.fromId(id);
return isBrowserWindow(win) ? win as any as BWT : null;
};
BrowserWindow.getAllWindows = () => {
return BaseWindow.getAllWindows().filter(isBrowserWindow) as any[] as BWT[];
};
BrowserWindow.getFocusedWindow = () => {
for (const window of BrowserWindow.getAllWindows()) {
if (window.isFocused() || window.isDevToolsFocused()) return window;
}
return null;
};
BrowserWindow.fromWebContents = (webContents: WebContents) => {
for (const window of BrowserWindow.getAllWindows()) {
if (window.webContents && window.webContents.equal(webContents)) return window;
}
return null;
};
BrowserWindow.fromBrowserView = (browserView: BrowserView) => {
for (const window of BrowserWindow.getAllWindows()) {
if (window.getBrowserView() === browserView) return window;
}
return null;
};
BrowserWindow.prototype.setTouchBar = function (touchBar) {
(TouchBar as any)._setOnWindow(touchBar, this);
};
// Forwarded to webContents:
BrowserWindow.prototype.loadURL = function (...args) {
return this.webContents.loadURL(...args);
};
BrowserWindow.prototype.getURL = function () {
return this.webContents.getURL();
};
BrowserWindow.prototype.loadFile = function (...args) {
return this.webContents.loadFile(...args);
};
BrowserWindow.prototype.reload = function (...args) {
return this.webContents.reload(...args);
};
BrowserWindow.prototype.send = function (...args) {
return this.webContents.send(...args);
};
BrowserWindow.prototype.openDevTools = function (...args) {
return this.webContents.openDevTools(...args);
};
BrowserWindow.prototype.closeDevTools = function () {
return this.webContents.closeDevTools();
};
BrowserWindow.prototype.isDevToolsOpened = function () {
return this.webContents.isDevToolsOpened();
};
BrowserWindow.prototype.isDevToolsFocused = function () {
return this.webContents.isDevToolsFocused();
};
BrowserWindow.prototype.toggleDevTools = function () {
return this.webContents.toggleDevTools();
};
BrowserWindow.prototype.inspectElement = function (...args) {
return this.webContents.inspectElement(...args);
};
BrowserWindow.prototype.inspectSharedWorker = function () {
return this.webContents.inspectSharedWorker();
};
BrowserWindow.prototype.inspectServiceWorker = function () {
return this.webContents.inspectServiceWorker();
};
BrowserWindow.prototype.showDefinitionForSelection = function () {
return this.webContents.showDefinitionForSelection();
};
BrowserWindow.prototype.capturePage = function (...args) {
return this.webContents.capturePage(...args);
};
BrowserWindow.prototype.getBackgroundThrottling = function () {
return this.webContents.getBackgroundThrottling();
};
BrowserWindow.prototype.setBackgroundThrottling = function (allowed: boolean) {
return this.webContents.setBackgroundThrottling(allowed);
};
module.exports = BrowserWindow;

View File

@@ -0,0 +1,2 @@
'use strict';
module.exports = process.electronBinding('content_tracing');

View File

@@ -1 +0,0 @@
export default process._linkedBinding('electron_browser_content_tracing');

View File

@@ -1,6 +1,6 @@
import { app, deprecate } from 'electron';
const binding = process._linkedBinding('electron_browser_crash_reporter');
const binding = process.electronBinding('crash_reporter');
class CrashReporter {
start (options: Electron.CrashReporterStartOptions) {

266
lib/browser/api/dialog.js Normal file
View File

@@ -0,0 +1,266 @@
'use strict';
const { app, BrowserWindow, deprecate } = require('electron');
const binding = process.electronBinding('dialog');
const v8Util = process.electronBinding('v8_util');
const DialogType = {
OPEN: 'OPEN',
SAVE: 'SAVE'
};
const saveFileDialogProperties = {
createDirectory: 1 << 0,
showHiddenFiles: 1 << 1,
treatPackageAsDirectory: 1 << 2,
showOverwriteConfirmation: 1 << 3,
dontAddToRecent: 1 << 4
};
const openFileDialogProperties = {
openFile: 1 << 0,
openDirectory: 1 << 1,
multiSelections: 1 << 2,
createDirectory: 1 << 3, // macOS
showHiddenFiles: 1 << 4,
promptToCreate: 1 << 5, // Windows
noResolveAliases: 1 << 6, // macOS
treatPackageAsDirectory: 1 << 7, // macOS
dontAddToRecent: 1 << 8 // Windows
};
const normalizeAccessKey = (text) => {
if (typeof text !== 'string') return text;
// macOS does not have access keys so remove single ampersands
// and replace double ampersands with a single ampersand
if (process.platform === 'darwin') {
return text.replace(/&(&?)/g, '$1');
}
// Linux uses a single underscore as an access key prefix so escape
// existing single underscores with a second underscore, replace double
// ampersands with a single ampersand, and replace a single ampersand with
// a single underscore
if (process.platform === 'linux') {
return text.replace(/_/g, '__').replace(/&(.?)/g, (match, after) => {
if (after === '&') return after;
return `_${after}`;
});
}
return text;
};
const checkAppInitialized = function () {
if (!app.isReady()) {
throw new Error('dialog module can only be used after app is ready');
}
};
const setupDialogProperties = (type, properties) => {
const dialogPropertiesTypes = (type === DialogType.OPEN) ? openFileDialogProperties : saveFileDialogProperties;
let dialogProperties = 0;
for (const prop in dialogPropertiesTypes) {
if (properties.includes(prop)) {
dialogProperties |= dialogPropertiesTypes[prop];
}
}
return dialogProperties;
};
const saveDialog = (sync, window, options) => {
checkAppInitialized();
if (window && window.constructor !== BrowserWindow) {
options = window;
window = null;
}
if (options == null) options = { title: 'Save' };
const {
buttonLabel = '',
defaultPath = '',
filters = [],
properties = [],
title = '',
message = '',
securityScopedBookmarks = false,
nameFieldLabel = '',
showsTagField = true
} = options;
if (typeof title !== 'string') throw new TypeError('Title must be a string');
if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string');
if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string');
if (typeof message !== 'string') throw new TypeError('Message must be a string');
if (typeof nameFieldLabel !== 'string') throw new TypeError('Name field label must be a string');
const settings = { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window };
settings.properties = setupDialogProperties(DialogType.SAVE, properties);
return (sync) ? binding.showSaveDialogSync(settings) : binding.showSaveDialog(settings);
};
const openDialog = (sync, window, options) => {
checkAppInitialized();
if (window && window.constructor !== BrowserWindow) {
options = window;
window = null;
}
if (options == null) {
options = {
title: 'Open',
properties: ['openFile']
};
}
const {
buttonLabel = '',
defaultPath = '',
filters = [],
properties = ['openFile'],
title = '',
message = '',
securityScopedBookmarks = false
} = options;
if (!Array.isArray(properties)) throw new TypeError('Properties must be an array');
if (typeof title !== 'string') throw new TypeError('Title must be a string');
if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string');
if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string');
if (typeof message !== 'string') throw new TypeError('Message must be a string');
const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, window };
settings.properties = setupDialogProperties(DialogType.OPEN, properties);
return (sync) ? binding.showOpenDialogSync(settings) : binding.showOpenDialog(settings);
};
const messageBox = (sync, window, options) => {
checkAppInitialized();
if (window && window.constructor !== BrowserWindow) {
options = window;
window = null;
}
if (options == null) options = { type: 'none' };
const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'];
const messageBoxOptions = { noLink: 1 << 0 };
let {
buttons = [],
cancelId,
checkboxLabel = '',
checkboxChecked,
defaultId = -1,
detail = '',
icon = null,
noLink = false,
message = '',
title = '',
type = 'none'
} = options;
const messageBoxType = messageBoxTypes.indexOf(type);
if (messageBoxType === -1) throw new TypeError('Invalid message box type');
if (!Array.isArray(buttons)) throw new TypeError('Buttons must be an array');
if (options.normalizeAccessKeys) buttons = buttons.map(normalizeAccessKey);
if (typeof title !== 'string') throw new TypeError('Title must be a string');
if (typeof noLink !== 'boolean') throw new TypeError('noLink must be a boolean');
if (typeof message !== 'string') throw new TypeError('Message must be a string');
if (typeof detail !== 'string') throw new TypeError('Detail must be a string');
if (typeof checkboxLabel !== 'string') throw new TypeError('checkboxLabel must be a string');
checkboxChecked = !!checkboxChecked;
if (checkboxChecked && !checkboxLabel) {
throw new Error('checkboxChecked requires that checkboxLabel also be passed');
}
// 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;
for (let i = 0; i < buttons.length; i++) {
const text = buttons[i].toLowerCase();
if (text === 'cancel' || text === 'no') {
cancelId = i;
break;
}
}
}
const settings = {
window,
messageBoxType,
buttons,
defaultId,
cancelId,
noLink,
title,
message,
detail,
checkboxLabel,
checkboxChecked,
icon
};
if (sync) {
return binding.showMessageBoxSync(settings);
} else {
return binding.showMessageBox(settings);
}
};
module.exports = {
showOpenDialog: function (window, options) {
return openDialog(false, window, options);
},
showOpenDialogSync: function (window, options) {
return openDialog(true, window, options);
},
showSaveDialog: function (window, options) {
return saveDialog(false, window, options);
},
showSaveDialogSync: function (window, options) {
return saveDialog(true, window, options);
},
showMessageBox: function (window, options) {
return messageBox(false, window, options);
},
showMessageBoxSync: function (window, options) {
return messageBox(true, window, options);
},
showErrorBox: function (...args) {
return binding.showErrorBox(...args);
},
showCertificateTrustDialog: function (window, options) {
if (window && window.constructor !== BrowserWindow) options = window;
if (options == null || typeof options !== 'object') {
throw new TypeError('options must be an object');
}
const { certificate, message = '' } = options;
if (certificate == null || typeof certificate !== 'object') {
throw new TypeError('certificate must be an object');
}
if (typeof message !== 'string') throw new TypeError('message must be a string');
return binding.showCertificateTrustDialog(window, certificate, message);
}
};

View File

@@ -1,306 +0,0 @@
import { app, BrowserWindow } from 'electron';
import { OpenDialogOptions, OpenDialogReturnValue, MessageBoxOptions, SaveDialogOptions, SaveDialogReturnValue, MessageBoxReturnValue, CertificateTrustDialogOptions } from 'electron/main';
const dialogBinding = process._linkedBinding('electron_browser_dialog');
const DialogType = {
OPEN: 'OPEN' as 'OPEN',
SAVE: 'SAVE' as 'SAVE'
};
enum SaveFileDialogProperties {
createDirectory = 1 << 0,
showHiddenFiles = 1 << 1,
treatPackageAsDirectory = 1 << 2,
showOverwriteConfirmation = 1 << 3,
dontAddToRecent = 1 << 4
}
enum OpenFileDialogProperties {
openFile = 1 << 0,
openDirectory = 1 << 1,
multiSelections = 1 << 2,
createDirectory = 1 << 3, // macOS
showHiddenFiles = 1 << 4,
promptToCreate = 1 << 5, // Windows
noResolveAliases = 1 << 6, // macOS
treatPackageAsDirectory = 1 << 7, // macOS
dontAddToRecent = 1 << 8 // Windows
}
const normalizeAccessKey = (text: string) => {
if (typeof text !== 'string') return text;
// macOS does not have access keys so remove single ampersands
// and replace double ampersands with a single ampersand
if (process.platform === 'darwin') {
return text.replace(/&(&?)/g, '$1');
}
// Linux uses a single underscore as an access key prefix so escape
// existing single underscores with a second underscore, replace double
// ampersands with a single ampersand, and replace a single ampersand with
// a single underscore
if (process.platform === 'linux') {
return text.replace(/_/g, '__').replace(/&(.?)/g, (match, after) => {
if (after === '&') return after;
return `_${after}`;
});
}
return text;
};
const checkAppInitialized = function () {
if (!app.isReady()) {
throw new Error('dialog module can only be used after app is ready');
}
};
const setupOpenDialogProperties = (properties: (keyof typeof OpenFileDialogProperties)[]): number => {
let dialogProperties = 0;
for (const property of properties) {
if (Object.prototype.hasOwnProperty.call(OpenFileDialogProperties, property)) { dialogProperties |= OpenFileDialogProperties[property]; }
}
return dialogProperties;
};
const setupSaveDialogProperties = (properties: (keyof typeof SaveFileDialogProperties)[]): number => {
let dialogProperties = 0;
for (const property of properties) {
if (Object.prototype.hasOwnProperty.call(SaveFileDialogProperties, property)) { dialogProperties |= SaveFileDialogProperties[property]; }
}
return dialogProperties;
};
const setupDialogProperties = (type: keyof typeof DialogType, properties: string[]): number => {
if (type === DialogType.OPEN) {
return setupOpenDialogProperties(properties as (keyof typeof OpenFileDialogProperties)[]);
} else if (type === DialogType.SAVE) {
return setupSaveDialogProperties(properties as (keyof typeof SaveFileDialogProperties)[]);
} else {
return 0;
}
};
const saveDialog = (sync: boolean, window: BrowserWindow | null, options?: SaveDialogOptions) => {
checkAppInitialized();
if (options == null) options = { title: 'Save' };
const {
buttonLabel = '',
defaultPath = '',
filters = [],
properties = [],
title = '',
message = '',
securityScopedBookmarks = false,
nameFieldLabel = '',
showsTagField = true
} = options;
if (typeof title !== 'string') throw new TypeError('Title must be a string');
if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string');
if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string');
if (typeof message !== 'string') throw new TypeError('Message must be a string');
if (typeof nameFieldLabel !== 'string') throw new TypeError('Name field label must be a string');
const settings = {
buttonLabel,
defaultPath,
filters,
title,
message,
securityScopedBookmarks,
nameFieldLabel,
showsTagField,
window,
properties: setupDialogProperties(DialogType.SAVE, properties)
};
return sync ? dialogBinding.showSaveDialogSync(settings) : dialogBinding.showSaveDialog(settings);
};
const openDialog = (sync: boolean, window: BrowserWindow | null, options?: OpenDialogOptions) => {
checkAppInitialized();
if (options == null) {
options = {
title: 'Open',
properties: ['openFile']
};
}
const {
buttonLabel = '',
defaultPath = '',
filters = [],
properties = ['openFile'],
title = '',
message = '',
securityScopedBookmarks = false
} = options;
if (!Array.isArray(properties)) throw new TypeError('Properties must be an array');
if (typeof title !== 'string') throw new TypeError('Title must be a string');
if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string');
if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string');
if (typeof message !== 'string') throw new TypeError('Message must be a string');
const settings = {
title,
buttonLabel,
defaultPath,
filters,
message,
securityScopedBookmarks,
window,
properties: setupDialogProperties(DialogType.OPEN, properties)
};
return (sync) ? dialogBinding.showOpenDialogSync(settings) : dialogBinding.showOpenDialog(settings);
};
const messageBox = (sync: boolean, window: BrowserWindow | null, options?: MessageBoxOptions) => {
checkAppInitialized();
if (options == null) options = { type: 'none', message: '' };
const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'];
let {
buttons = [],
cancelId,
checkboxLabel = '',
checkboxChecked,
defaultId = -1,
detail = '',
icon = null,
noLink = false,
message = '',
title = '',
type = 'none'
} = options;
const messageBoxType = messageBoxTypes.indexOf(type);
if (messageBoxType === -1) throw new TypeError('Invalid message box type');
if (!Array.isArray(buttons)) throw new TypeError('Buttons must be an array');
if (options.normalizeAccessKeys) buttons = buttons.map(normalizeAccessKey);
if (typeof title !== 'string') throw new TypeError('Title must be a string');
if (typeof noLink !== 'boolean') throw new TypeError('noLink must be a boolean');
if (typeof message !== 'string') throw new TypeError('Message must be a string');
if (typeof detail !== 'string') throw new TypeError('Detail must be a string');
if (typeof checkboxLabel !== 'string') throw new TypeError('checkboxLabel must be a string');
checkboxChecked = !!checkboxChecked;
if (checkboxChecked && !checkboxLabel) {
throw new Error('checkboxChecked requires that checkboxLabel also be passed');
}
// 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;
for (let i = 0; i < buttons.length; i++) {
const text = buttons[i].toLowerCase();
if (text === 'cancel' || text === 'no') {
cancelId = i;
break;
}
}
}
const settings = {
window,
messageBoxType,
buttons,
defaultId,
cancelId,
noLink,
title,
message,
detail,
checkboxLabel,
checkboxChecked,
icon
};
if (sync) {
return dialogBinding.showMessageBoxSync(settings);
} else {
return dialogBinding.showMessageBox(settings);
}
};
// eat dirt, eslint
/* eslint-disable import/export */
export function showOpenDialog(window: BrowserWindow, options: OpenDialogOptions): OpenDialogReturnValue;
export function showOpenDialog(options: OpenDialogOptions): OpenDialogReturnValue;
export function showOpenDialog (windowOrOptions: BrowserWindow | OpenDialogOptions, maybeOptions?: OpenDialogOptions): OpenDialogReturnValue {
const window = (windowOrOptions && !(windowOrOptions instanceof BrowserWindow) ? null : windowOrOptions);
const options = (windowOrOptions && !(windowOrOptions instanceof BrowserWindow) ? windowOrOptions : maybeOptions);
return openDialog(false, window, options);
}
export function showOpenDialogSync(window: BrowserWindow, options: OpenDialogOptions): OpenDialogReturnValue;
export function showOpenDialogSync(options: OpenDialogOptions): OpenDialogReturnValue;
export function showOpenDialogSync (windowOrOptions: BrowserWindow | OpenDialogOptions, maybeOptions?: OpenDialogOptions): OpenDialogReturnValue {
const window = (windowOrOptions && !(windowOrOptions instanceof BrowserWindow) ? null : windowOrOptions);
const options = (windowOrOptions && !(windowOrOptions instanceof BrowserWindow) ? windowOrOptions : maybeOptions);
return openDialog(true, window, options);
}
export function showSaveDialog(window: BrowserWindow, options: SaveDialogOptions): SaveDialogReturnValue;
export function showSaveDialog(options: SaveDialogOptions): SaveDialogReturnValue;
export function showSaveDialog (windowOrOptions: BrowserWindow | SaveDialogOptions, maybeOptions?: SaveDialogOptions): SaveDialogReturnValue {
const window = (windowOrOptions && !(windowOrOptions instanceof BrowserWindow) ? null : windowOrOptions);
const options = (windowOrOptions && !(windowOrOptions instanceof BrowserWindow) ? windowOrOptions : maybeOptions);
return saveDialog(false, window, options);
}
export function showSaveDialogSync(window: BrowserWindow, options: SaveDialogOptions): SaveDialogReturnValue;
export function showSaveDialogSync(options: SaveDialogOptions): SaveDialogReturnValue;
export function showSaveDialogSync (windowOrOptions: BrowserWindow | SaveDialogOptions, maybeOptions?: SaveDialogOptions): SaveDialogReturnValue {
const window = (windowOrOptions && !(windowOrOptions instanceof BrowserWindow) ? null : windowOrOptions);
const options = (windowOrOptions && !(windowOrOptions instanceof BrowserWindow) ? windowOrOptions : maybeOptions);
return saveDialog(true, window, options);
}
export function showMessageBox(window: BrowserWindow, options: MessageBoxOptions): MessageBoxReturnValue;
export function showMessageBox(options: MessageBoxOptions): MessageBoxReturnValue;
export function showMessageBox (windowOrOptions: BrowserWindow | MessageBoxOptions, maybeOptions?: MessageBoxOptions): MessageBoxReturnValue {
const window = (windowOrOptions && !(windowOrOptions instanceof BrowserWindow) ? null : windowOrOptions);
const options = (windowOrOptions && !(windowOrOptions instanceof BrowserWindow) ? windowOrOptions : maybeOptions);
return messageBox(false, window, options);
}
export function showMessageBoxSync(window: BrowserWindow, options: MessageBoxOptions): MessageBoxReturnValue;
export function showMessageBoxSync(options: MessageBoxOptions): MessageBoxReturnValue;
export function showMessageBoxSync (windowOrOptions: BrowserWindow | MessageBoxOptions, maybeOptions?: MessageBoxOptions): MessageBoxReturnValue {
const window = (windowOrOptions && !(windowOrOptions instanceof BrowserWindow) ? null : windowOrOptions);
const options = (windowOrOptions && !(windowOrOptions instanceof BrowserWindow) ? windowOrOptions : maybeOptions);
return messageBox(true, window, options);
}
export function showErrorBox (...args: any[]) {
return dialogBinding.showErrorBox(...args);
}
export function showCertificateTrustDialog (windowOrOptions: BrowserWindow | CertificateTrustDialogOptions, maybeOptions?: CertificateTrustDialogOptions) {
const window = (windowOrOptions && !(windowOrOptions instanceof BrowserWindow) ? null : windowOrOptions);
const options = (windowOrOptions && !(windowOrOptions instanceof BrowserWindow) ? windowOrOptions : maybeOptions);
if (options == null || typeof options !== 'object') {
throw new TypeError('options must be an object');
}
const { certificate, message = '' } = options;
if (certificate == null || typeof certificate !== 'object') {
throw new TypeError('certificate must be an object');
}
if (typeof message !== 'string') throw new TypeError('message must be a string');
return dialogBinding.showCertificateTrustDialog(window, certificate, message);
}

View File

@@ -0,0 +1,3 @@
'use strict';
module.exports = process.electronBinding('global_shortcut').globalShortcut;

View File

@@ -1,2 +0,0 @@
const { globalShortcut } = process._linkedBinding('electron_browser_global_shortcut');
export default globalShortcut;

View File

@@ -1,24 +1,22 @@
import { EventEmitter } from 'events';
'use strict';
let _inAppPurchase;
const { deprecate } = require('electron');
if (process.platform === 'darwin') {
const { inAppPurchase, InAppPurchase } = process._linkedBinding('electron_browser_in_app_purchase');
const { EventEmitter } = require('events');
const { inAppPurchase, InAppPurchase } = process.electronBinding('in_app_purchase');
// inAppPurchase is an EventEmitter.
Object.setPrototypeOf(InAppPurchase.prototype, EventEmitter.prototype);
EventEmitter.call(inAppPurchase);
_inAppPurchase = inAppPurchase;
module.exports = inAppPurchase;
} else {
_inAppPurchase = new EventEmitter();
Object.assign(_inAppPurchase, {
purchaseProduct: () => {
module.exports = {
purchaseProduct: (productID, quantity, callback) => {
throw new Error('The inAppPurchase module can only be used on macOS');
},
canMakePayments: () => false,
getReceiptURL: () => ''
});
};
}
export default _inAppPurchase;

View File

@@ -1,56 +1,44 @@
import { app, BrowserWindow, WebContents, MenuItemConstructorOptions } from 'electron';
'use strict';
const { app } = require('electron');
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' | 'startspeaking' | 'stopspeaking' |
'toggledevtools' | 'togglefullscreen' | 'undo' | 'unhide' | 'window' | 'zoom' | 'zoomin' | 'zoomout' | 'appmenu' | 'filemenu' | 'editmenu' | 'viewmenu' | 'windowmenu'
interface Role {
label: string;
accelerator?: string;
windowMethod?: ((window: BrowserWindow) => void);
webContentsMethod?: ((webContents: WebContents) => void);
appMethod?: () => void;
registerAccelerator?: boolean;
nonNativeMacOSRole?: boolean;
submenu?: MenuItemConstructorOptions[];
}
export const roleList: Record<RoleId, Role> = {
const roles = {
about: {
get label () {
return isLinux ? 'About' : `About ${app.name}`;
},
...(isWindows && { appMethod: () => app.showAboutPanel() })
...(isWindows && { appMethod: 'showAboutPanel' })
},
close: {
label: isMac ? 'Close Window' : 'Close',
accelerator: 'CommandOrControl+W',
windowMethod: w => w.close()
windowMethod: 'close'
},
copy: {
label: 'Copy',
accelerator: 'CommandOrControl+C',
webContentsMethod: wc => wc.copy(),
webContentsMethod: 'copy',
registerAccelerator: false
},
cut: {
label: 'Cut',
accelerator: 'CommandOrControl+X',
webContentsMethod: wc => wc.cut(),
webContentsMethod: 'cut',
registerAccelerator: false
},
delete: {
label: 'Delete',
webContentsMethod: wc => wc.delete()
webContentsMethod: 'delete'
},
forcereload: {
label: 'Force Reload',
accelerator: 'Shift+CmdOrCtrl+R',
nonNativeMacOSRole: true,
windowMethod: (window: BrowserWindow) => {
windowMethod: (window) => {
window.webContents.reloadIgnoringCache();
}
},
@@ -73,18 +61,18 @@ export const roleList: Record<RoleId, Role> = {
minimize: {
label: 'Minimize',
accelerator: 'CommandOrControl+M',
windowMethod: w => w.minimize()
windowMethod: 'minimize'
},
paste: {
label: 'Paste',
accelerator: 'CommandOrControl+V',
webContentsMethod: wc => wc.paste(),
webContentsMethod: 'paste',
registerAccelerator: false
},
pasteandmatchstyle: {
label: 'Paste and Match Style',
accelerator: isMac ? 'Cmd+Option+Shift+V' : 'Shift+CommandOrControl+V',
webContentsMethod: wc => wc.pasteAndMatchStyle(),
accelerator: 'Shift+CommandOrControl+V',
webContentsMethod: 'pasteAndMatchStyle',
registerAccelerator: false
},
quit: {
@@ -96,31 +84,31 @@ export const roleList: Record<RoleId, Role> = {
}
},
accelerator: isWindows ? undefined : 'CommandOrControl+Q',
appMethod: () => app.quit()
appMethod: 'quit'
},
redo: {
label: 'Redo',
accelerator: isWindows ? 'Control+Y' : 'Shift+CommandOrControl+Z',
webContentsMethod: wc => wc.redo()
webContentsMethod: 'redo'
},
reload: {
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
nonNativeMacOSRole: true,
windowMethod: w => w.reload()
windowMethod: 'reload'
},
resetzoom: {
label: 'Actual Size',
accelerator: 'CommandOrControl+0',
nonNativeMacOSRole: true,
webContentsMethod: (webContents: WebContents) => {
webContentsMethod: (webContents) => {
webContents.zoomLevel = 0;
}
},
selectall: {
label: 'Select All',
accelerator: 'CommandOrControl+A',
webContentsMethod: wc => wc.selectAll()
webContentsMethod: 'selectAll'
},
services: {
label: 'Services'
@@ -141,19 +129,19 @@ export const roleList: Record<RoleId, Role> = {
label: 'Toggle Developer Tools',
accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I',
nonNativeMacOSRole: true,
windowMethod: w => w.webContents.toggleDevTools()
windowMethod: 'toggleDevTools'
},
togglefullscreen: {
label: 'Toggle Full Screen',
accelerator: isMac ? 'Control+Command+F' : 'F11',
windowMethod: (window: BrowserWindow) => {
windowMethod: (window) => {
window.setFullScreen(!window.isFullScreen());
}
},
undo: {
label: 'Undo',
accelerator: 'CommandOrControl+Z',
webContentsMethod: wc => wc.undo()
webContentsMethod: 'undo'
},
unhide: {
label: 'Show All'
@@ -168,7 +156,7 @@ export const roleList: Record<RoleId, Role> = {
label: 'Zoom In',
accelerator: 'CommandOrControl+Plus',
nonNativeMacOSRole: true,
webContentsMethod: (webContents: WebContents) => {
webContentsMethod: (webContents) => {
webContents.zoomLevel += 0.5;
}
},
@@ -176,7 +164,7 @@ export const roleList: Record<RoleId, Role> = {
label: 'Zoom Out',
accelerator: 'CommandOrControl+-',
nonNativeMacOSRole: true,
webContentsMethod: (webContents: WebContents) => {
webContentsMethod: (webContents) => {
webContents.zoomLevel -= 0.5;
}
},
@@ -226,11 +214,11 @@ export const roleList: Record<RoleId, Role> = {
{ role: 'stopSpeaking' }
]
}
] as MenuItemConstructorOptions[] : [
] : [
{ role: 'delete' },
{ type: 'separator' },
{ role: 'selectAll' }
] as MenuItemConstructorOptions[])
])
]
},
// View submenu
@@ -257,38 +245,40 @@ export const roleList: Record<RoleId, Role> = {
...(isMac ? [
{ type: 'separator' },
{ role: 'front' }
] as MenuItemConstructorOptions[] : [
] : [
{ role: 'close' }
] as MenuItemConstructorOptions[])
])
]
}
};
const canExecuteRole = (role: keyof typeof roleList) => {
if (!Object.prototype.hasOwnProperty.call(roleList, role)) return false;
exports.roleList = roles;
const canExecuteRole = (role) => {
if (!Object.prototype.hasOwnProperty.call(roles, role)) return false;
if (!isMac) return true;
// macOS handles all roles natively except for a few
return roleList[role].nonNativeMacOSRole;
return roles[role].nonNativeMacOSRole;
};
export function getDefaultLabel (role: RoleId) {
return Object.prototype.hasOwnProperty.call(roleList, role) ? roleList[role].label : '';
}
exports.getDefaultLabel = (role) => {
return Object.prototype.hasOwnProperty.call(roles, role) ? roles[role].label : '';
};
export function getDefaultAccelerator (role: RoleId) {
if (Object.prototype.hasOwnProperty.call(roleList, role)) return roleList[role].accelerator;
}
exports.getDefaultAccelerator = (role) => {
if (Object.prototype.hasOwnProperty.call(roles, role)) return roles[role].accelerator;
};
export function shouldRegisterAccelerator (role: RoleId) {
const hasRoleRegister = Object.prototype.hasOwnProperty.call(roleList, role) && roleList[role].registerAccelerator !== undefined;
return hasRoleRegister ? roleList[role].registerAccelerator : true;
}
exports.shouldRegisterAccelerator = (role) => {
const hasRoleRegister = Object.prototype.hasOwnProperty.call(roles, role) && roles[role].registerAccelerator !== undefined;
return hasRoleRegister ? roles[role].registerAccelerator : true;
};
export function getDefaultSubmenu (role: RoleId) {
if (!Object.prototype.hasOwnProperty.call(roleList, role)) return;
exports.getDefaultSubmenu = (role) => {
if (!Object.prototype.hasOwnProperty.call(roles, role)) return;
let { submenu } = roleList[role];
let { submenu } = roles[role];
// remove null items from within the submenu
if (Array.isArray(submenu)) {
@@ -296,27 +286,35 @@ export function getDefaultSubmenu (role: RoleId) {
}
return submenu;
}
};
export function execute (role: RoleId, focusedWindow: BrowserWindow, focusedWebContents: WebContents) {
exports.execute = (role, focusedWindow, focusedWebContents) => {
if (!canExecuteRole(role)) return false;
const { appMethod, webContentsMethod, windowMethod } = roleList[role];
const { appMethod, webContentsMethod, windowMethod } = roles[role];
if (appMethod) {
appMethod();
app[appMethod]();
return true;
}
if (windowMethod && focusedWindow != null) {
windowMethod(focusedWindow);
if (typeof windowMethod === 'function') {
windowMethod(focusedWindow);
} else {
focusedWindow[windowMethod]();
}
return true;
}
if (webContentsMethod && focusedWebContents != null) {
webContentsMethod(focusedWebContents);
if (typeof webContentsMethod === 'function') {
webContentsMethod(focusedWebContents);
} else {
focusedWebContents[webContentsMethod]();
}
return true;
}
return false;
}
};

View File

@@ -1,9 +1,12 @@
import * as roles from './menu-item-roles';
import { Menu, Event, BrowserWindow, WebContents } from 'electron';
'use strict';
const roles = require('@electron/internal/browser/api/menu-item-roles');
let nextCommandId = 0;
const MenuItem = function (this: any, options: any) {
const MenuItem = function (options) {
const { Menu } = require('electron');
// Preserve extra fields specified by user
for (const key in options) {
if (!(key in this)) this[key] = options[key];
@@ -44,7 +47,7 @@ const MenuItem = function (this: any, options: any) {
this.overrideReadOnlyProperty('commandId', ++nextCommandId);
const click = options.click;
this.click = (event: Event, focusedWindow: BrowserWindow, focusedWebContents: WebContents) => {
this.click = (event, focusedWindow, focusedWebContents) => {
// Manually flip the checked flags when clicked.
if (this.type === 'checkbox' || this.type === 'radio') {
this.checked = !this.checked;
@@ -66,13 +69,13 @@ MenuItem.prototype.getDefaultRoleAccelerator = function () {
return roles.getDefaultAccelerator(this.role);
};
MenuItem.prototype.overrideProperty = function (name: string, defaultValue: any = null) {
MenuItem.prototype.overrideProperty = function (name, defaultValue = null) {
if (this[name] == null) {
this[name] = defaultValue;
}
};
MenuItem.prototype.overrideReadOnlyProperty = function (name: string, defaultValue: any) {
MenuItem.prototype.overrideReadOnlyProperty = function (name, defaultValue) {
this.overrideProperty(name, defaultValue);
Object.defineProperty(this, name, {
enumerable: true,

View File

@@ -1,4 +1,6 @@
function splitArray<T> (arr: T[], predicate: (x: T) => boolean) {
'use strict';
function splitArray (arr, predicate) {
const result = arr.reduce((multi, item) => {
const current = multi[multi.length - 1];
if (predicate(item)) {
@@ -7,7 +9,7 @@ function splitArray<T> (arr: T[], predicate: (x: T) => boolean) {
current.push(item);
}
return multi;
}, [[]] as T[][]);
}, [[]]);
if (result[result.length - 1].length === 0) {
return result.slice(0, result.length - 1);
@@ -15,7 +17,7 @@ function splitArray<T> (arr: T[], predicate: (x: T) => boolean) {
return result;
}
function joinArrays (arrays: any[][], joinIDs: any[]) {
function joinArrays (arrays, joinIDs) {
return arrays.reduce((joined, arr, i) => {
if (i > 0 && arr.length) {
if (joinIDs.length > 0) {
@@ -29,14 +31,14 @@ function joinArrays (arrays: any[][], joinIDs: any[]) {
}, []);
}
function pushOntoMultiMap<K, V> (map: Map<K, V[]>, key: K, value: V) {
function pushOntoMultiMap (map, key, value) {
if (!map.has(key)) {
map.set(key, []);
}
map.get(key)!.push(value);
map.get(key).push(value);
}
function indexOfGroupContainingID<T> (groups: {id?: T}[][], id: T, ignoreGroup: {id?: T}[]) {
function indexOfGroupContainingID (groups, id, ignoreGroup) {
return groups.findIndex(
candidateGroup =>
candidateGroup !== ignoreGroup &&
@@ -48,11 +50,11 @@ function indexOfGroupContainingID<T> (groups: {id?: T}[][], id: T, ignoreGroup:
// Sort nodes topologically using a depth-first approach. Encountered cycles
// are broken.
function sortTopologically<T> (originalOrder: T[], edgesById: Map<T, T[]>) {
const sorted = [] as T[];
const marked = new Set<T>();
function sortTopologically (originalOrder, edgesById) {
const sorted = [];
const marked = new Set();
const visit = (mark: T) => {
const visit = (mark) => {
if (marked.has(mark)) return;
marked.add(mark);
const edges = edgesById.get(mark);
@@ -66,7 +68,7 @@ function sortTopologically<T> (originalOrder: T[], edgesById: Map<T, T[]>) {
return sorted;
}
function attemptToMergeAGroup<T> (groups: {before?: T[], after?: T[], id?: T}[][]) {
function attemptToMergeAGroup (groups) {
for (let i = 0; i < groups.length; i++) {
const group = groups[i];
for (const item of group) {
@@ -85,7 +87,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 (groups) {
let merged = true;
while (merged) {
merged = attemptToMergeAGroup(groups);
@@ -93,7 +95,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 (group) {
const originalOrder = group.map((node, i) => i);
const edges = new Map();
const idToIndex = new Map(group.map((item, i) => [item.id, i]));
@@ -121,7 +123,7 @@ function sortItemsInGroup<T> (group: {before?: T[], after?: T[], id?: T}[]) {
return sortedNodes.map(i => group[i]);
}
function findEdgesInGroup<T> (groups: {beforeGroupContaining?: T[], afterGroupContaining?: T[], id?: T}[][], i: number, edges: Map<any, any>) {
function findEdgesInGroup (groups, i, edges) {
const group = groups[i];
for (const item of group) {
if (item.beforeGroupContaining) {
@@ -145,7 +147,7 @@ function findEdgesInGroup<T> (groups: {beforeGroupContaining?: T[], afterGroupCo
}
}
function sortGroups<T> (groups: {id?: T}[][]) {
function sortGroups (groups) {
const originalOrder = groups.map((item, i) => i);
const edges = new Map();
@@ -157,8 +159,8 @@ function sortGroups<T> (groups: {id?: T}[][]) {
return sortedGroupIndexes.map(i => groups[i]);
}
export function sortMenuItems (menuItems: {type?: string, id?: string}[]) {
const isSeparator = (item: {type?: string}) => item.type === 'separator';
function sortMenuItems (menuItems) {
const isSeparator = (item) => item.type === 'separator';
const separators = menuItems.filter(i => i.type === 'separator');
// Split the items into their implicit groups based upon separators.
@@ -170,3 +172,5 @@ export function sortMenuItems (menuItems: {type?: string, id?: string}[]) {
const joined = joinArrays(sortedGroups, separators);
return joined;
}
module.exports = { sortMenuItems };

View File

@@ -1,11 +1,13 @@
import { BaseWindow, MenuItem, webContents, Menu as MenuType, BrowserWindow, MenuItemConstructorOptions } from 'electron';
import { sortMenuItems } from './menu-utils';
'use strict';
const v8Util = process._linkedBinding('electron_common_v8_util');
const bindings = process._linkedBinding('electron_browser_menu');
const { TopLevelWindow, MenuItem, webContents } = require('electron');
const { sortMenuItems } = require('@electron/internal/browser/api/menu-utils');
const EventEmitter = require('events').EventEmitter;
const v8Util = process.electronBinding('v8_util');
const bindings = process.electronBinding('menu');
const { Menu } = bindings as { Menu: typeof MenuType };
let applicationMenu: MenuType | null = null;
const { Menu } = bindings;
let applicationMenu = null;
let groupIdIndex = 0;
/* Instance Methods */
@@ -17,17 +19,17 @@ Menu.prototype._init = function () {
};
Menu.prototype._isCommandIdChecked = function (id) {
return this.commandsMap[id] ? this.commandsMap[id].checked : false;
return this.commandsMap[id] ? this.commandsMap[id].checked : undefined;
};
Menu.prototype._isCommandIdEnabled = function (id) {
return this.commandsMap[id] ? this.commandsMap[id].enabled : false;
return this.commandsMap[id] ? this.commandsMap[id].enabled : undefined;
};
Menu.prototype._shouldCommandIdWorkWhenHidden = function (id) {
return this.commandsMap[id] ? !!this.commandsMap[id].acceleratorWorksWhenHidden : false;
return this.commandsMap[id] ? this.commandsMap[id].acceleratorWorksWhenHidden : undefined;
};
Menu.prototype._isCommandIdVisible = function (id) {
return this.commandsMap[id] ? this.commandsMap[id].visible : false;
return this.commandsMap[id] ? this.commandsMap[id].visible : undefined;
};
Menu.prototype._getAcceleratorForCommandId = function (id, useDefaultAccelerator) {
@@ -38,14 +40,13 @@ Menu.prototype._getAcceleratorForCommandId = function (id, useDefaultAccelerator
};
Menu.prototype._shouldRegisterAcceleratorForCommandId = function (id) {
return this.commandsMap[id] ? this.commandsMap[id].registerAccelerator : false;
return this.commandsMap[id] ? this.commandsMap[id].registerAccelerator : undefined;
};
Menu.prototype._executeCommand = function (event, id) {
const command = this.commandsMap[id];
if (!command) return;
const focusedWindow = BaseWindow.getFocusedWindow();
command.click(event, focusedWindow instanceof BrowserWindow ? focusedWindow : undefined, webContents.getFocusedWebContents());
command.click(event, TopLevelWindow.getFocusedWindow(), webContents.getFocusedWebContents());
};
Menu.prototype._menuWillShow = function () {
@@ -71,23 +72,23 @@ Menu.prototype.popup = function (options = {}) {
if (typeof positioningItem !== 'number') positioningItem = -1;
// find which window to use
const wins = BaseWindow.getAllWindows();
if (!wins || wins.indexOf(window as any) === -1) {
window = BaseWindow.getFocusedWindow() as any;
const wins = TopLevelWindow.getAllWindows();
if (!wins || wins.indexOf(window) === -1) {
window = TopLevelWindow.getFocusedWindow();
if (!window && wins && wins.length > 0) {
window = wins[0] as any;
window = wins[0];
}
if (!window) {
throw new Error('Cannot open Menu without a BaseWindow present');
throw new Error('Cannot open Menu without a TopLevelWindow present');
}
}
this.popupAt(window as unknown as BaseWindow, x, y, positioningItem, callback);
this.popupAt(window, x, y, positioningItem, callback);
return { browserWindow: window, x, y, position: positioningItem };
};
Menu.prototype.closePopup = function (window) {
if (window instanceof BaseWindow) {
if (window instanceof TopLevelWindow) {
this.closePopupAt(window.id);
} else {
// Passing -1 (invalid) would make closePopupAt close the all menu runners
@@ -101,9 +102,8 @@ Menu.prototype.getMenuItemById = function (id) {
let found = items.find(item => item.id === id) || null;
for (let i = 0; !found && i < items.length; i++) {
const { submenu } = items[i];
if (submenu) {
found = submenu.getMenuItemById(id);
if (items[i].submenu) {
found = items[i].submenu.getMenuItemById(id);
}
}
return found;
@@ -155,7 +155,7 @@ Menu.getApplicationMenu = () => applicationMenu;
Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder;
// set application menu with a preexisting menu
Menu.setApplicationMenu = function (menu: MenuType) {
Menu.setApplicationMenu = function (menu) {
if (menu && menu.constructor !== Menu) {
throw new TypeError('Invalid menu');
}
@@ -168,7 +168,7 @@ Menu.setApplicationMenu = function (menu: MenuType) {
menu._callMenuWillShow();
bindings.setApplicationMenu(menu);
} else {
const windows = BaseWindow.getAllWindows();
const windows = TopLevelWindow.getAllWindows();
return windows.map(w => w.setMenu(menu));
}
};
@@ -200,7 +200,7 @@ Menu.buildFromTemplate = function (template) {
/* Helper Functions */
// validate the template against having the wrong attribute
function areValidTemplateItems (template: (MenuItemConstructorOptions | MenuItem)[]) {
function areValidTemplateItems (template) {
return template.every(item =>
item != null &&
typeof item === 'object' &&
@@ -209,7 +209,7 @@ function areValidTemplateItems (template: (MenuItemConstructorOptions | MenuItem
item.type === 'separator'));
}
function sortTemplate (template: (MenuItemConstructorOptions | MenuItem)[]) {
function sortTemplate (template) {
const sorted = sortMenuItems(template);
for (const item of sorted) {
if (Array.isArray(item.submenu)) {
@@ -220,15 +220,15 @@ 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, pos) {
if (pos > 0) {
for (let idx = pos - 1; idx >= 0; idx--) {
if (items[idx].type === 'radio') return (items[idx] as any).groupId;
if (items[idx].type === 'radio') return items[idx].groupId;
if (items[idx].type === 'separator') break;
}
} else if (pos < items.length) {
for (let idx = pos; idx <= items.length - 1; idx++) {
if (items[idx].type === 'radio') return (items[idx] as any).groupId;
if (items[idx].type === 'radio') return items[idx].groupId;
if (items[idx].type === 'separator') break;
}
}
@@ -236,7 +236,7 @@ function generateGroupId (items: (MenuItemConstructorOptions | MenuItem)[], pos:
return groupIdIndex;
}
function removeExtraSeparators (items: (MenuItemConstructorOptions | MenuItem)[]) {
function removeExtraSeparators (items) {
// fold adjacent separators together
let ret = items.filter((e, idx, arr) => {
if (e.visible === false) return true;
@@ -252,7 +252,7 @@ function removeExtraSeparators (items: (MenuItemConstructorOptions | MenuItem)[]
return ret;
}
function insertItemByType (this: MenuType, item: MenuItem, pos: number) {
function insertItemByType (item, pos) {
const types = {
normal: () => this.insertItem(pos, item.commandId, item.label),
checkbox: () => this.insertCheckItem(pos, item.commandId, item.label),

View File

@@ -1,5 +1,5 @@
import { MessagePortMain } from '@electron/internal/browser/message-port-main';
const { createPair } = process._linkedBinding('electron_browser_message_port');
const { createPair } = process.electronBinding('message_port');
export default class MessageChannelMain {
port1: MessagePortMain;

View File

@@ -4,7 +4,6 @@
export const browserModuleList: ElectronInternal.ModuleEntry[] = [
{ name: 'app', loader: () => require('./app') },
{ name: 'autoUpdater', loader: () => require('./auto-updater') },
{ name: 'BaseWindow', loader: () => require('./base-window') },
{ name: 'BrowserView', loader: () => require('./browser-view') },
{ name: 'BrowserWindow', loader: () => require('./browser-window') },
{ name: 'contentTracing', loader: () => require('./content-tracing') },
@@ -26,6 +25,7 @@ export const browserModuleList: ElectronInternal.ModuleEntry[] = [
{ name: 'screen', loader: () => require('./screen') },
{ name: 'session', loader: () => require('./session') },
{ name: 'systemPreferences', loader: () => require('./system-preferences') },
{ name: 'TopLevelWindow', loader: () => require('./top-level-window') },
{ name: 'TouchBar', loader: () => require('./touch-bar') },
{ name: 'Tray', loader: () => require('./tray') },
{ name: 'View', loader: () => require('./view') },

View File

@@ -7,7 +7,6 @@
export const browserModuleNames = [
'app',
'autoUpdater',
'BaseWindow',
'BrowserView',
'BrowserWindow',
'contentTracing',
@@ -29,6 +28,7 @@ export const browserModuleNames = [
'screen',
'session',
'systemPreferences',
'TopLevelWindow',
'TouchBar',
'Tray',
'View',

View File

@@ -1,6 +1,6 @@
import { EventEmitter } from 'events';
const { NativeTheme, nativeTheme } = process._linkedBinding('electron_common_native_theme');
const { NativeTheme, nativeTheme } = process.electronBinding('native_theme');
Object.setPrototypeOf(NativeTheme.prototype, EventEmitter.prototype);
EventEmitter.call(nativeTheme as any);

View File

@@ -0,0 +1,32 @@
'use strict';
// TODO(deepak1556): Deprecate and remove standalone netLog module,
// it is now a property of session module.
const { app, session } = require('electron');
// Fallback to default session.
Object.setPrototypeOf(module.exports, new Proxy({}, {
get (target, property) {
if (!app.isReady()) return;
const netLog = session.defaultSession.netLog;
if (!Object.prototype.hasOwnProperty.call(Object.getPrototypeOf(netLog), property)) return;
// check for properties on the prototype chain that aren't functions
if (typeof netLog[property] !== 'function') return netLog[property];
// Returning a native function directly would throw error.
return (...args) => netLog[property](...args);
},
ownKeys () {
if (!app.isReady()) return [];
return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession.netLog));
},
getOwnPropertyDescriptor (target) {
return { configurable: true, enumerable: true };
}
}));

View File

@@ -1,22 +0,0 @@
// TODO(deepak1556): Deprecate and remove standalone netLog module,
// it is now a property of session module.
import { app, session } from 'electron';
const startLogging: typeof session.defaultSession.netLog.startLogging = async (path, options) => {
if (!app.isReady()) return;
return session.defaultSession.netLog.startLogging(path, options);
};
const stopLogging: typeof session.defaultSession.netLog.stopLogging = async () => {
if (!app.isReady()) return;
return session.defaultSession.netLog.stopLogging();
};
export default {
startLogging,
stopLogging,
get currentlyLogging (): boolean {
if (!app.isReady()) return false;
return session.defaultSession.netLog.currentlyLogging;
}
};

View File

@@ -2,13 +2,7 @@ import * as url from 'url';
import { Readable, Writable } from 'stream';
import { app } from 'electron';
import { ClientRequestConstructorOptions, UploadProgress } from 'electron/main';
const {
net,
Net,
isValidHeaderName,
isValidHeaderValue,
createURLLoader
} = process._linkedBinding('electron_browser_net');
const { net, Net, isValidHeaderName, isValidHeaderValue, createURLLoader } = process.electronBinding('net');
const kSupportedProtocols = new Set(['http:', 'https:']);

View File

@@ -0,0 +1,7 @@
'use strict';
const { Notification, isSupported } = process.electronBinding('notification');
Notification.isSupported = isSupported;
module.exports = Notification;

View File

@@ -1,8 +0,0 @@
const {
Notification: ElectronNotification,
isSupported
} = process._linkedBinding('electron_common_notification');
ElectronNotification.isSupported = isSupported;
export default ElectronNotification;

View File

@@ -1,11 +1,7 @@
import { EventEmitter } from 'events';
import { app } from 'electron';
const {
createPowerMonitor,
getSystemIdleState,
getSystemIdleTime
} = process._linkedBinding('electron_browser_power_monitor');
const { createPowerMonitor, getSystemIdleState, getSystemIdleTime } = process.electronBinding('power_monitor');
class PowerMonitor extends EventEmitter {
constructor () {

View File

@@ -0,0 +1,3 @@
'use strict';
module.exports = process.electronBinding('power_save_blocker').powerSaveBlocker;

View File

@@ -1,2 +0,0 @@
const { powerSaveBlocker } = process._linkedBinding('electron_browser_power_save_blocker');
export default powerSaveBlocker;

View File

@@ -1,7 +1,7 @@
import { app, session } from 'electron';
// Global protocol APIs.
const protocol = process._linkedBinding('electron_browser_protocol');
const protocol = process.electronBinding('protocol');
// Fallback protocol APIs of default session.
Object.setPrototypeOf(protocol, new Proxy({}, {

View File

@@ -2,7 +2,7 @@
import { createLazyInstance } from '../utils';
const { EventEmitter } = require('events');
const { Screen, createScreen } = process._linkedBinding('electron_common_screen');
const { Screen, createScreen } = process.electronBinding('screen');
// Screen is an EventEmitter.
Object.setPrototypeOf(Screen.prototype, EventEmitter.prototype);

View File

@@ -0,0 +1,17 @@
'use strict';
const { EventEmitter } = require('events');
const { app, deprecate } = require('electron');
const { fromPartition } = process.electronBinding('session');
// Public API.
Object.defineProperties(exports, {
defaultSession: {
enumerable: true,
get () { return fromPartition(''); }
},
fromPartition: {
enumerable: true,
value: fromPartition
}
});

View File

@@ -1,8 +0,0 @@
const { fromPartition } = process._linkedBinding('electron_browser_session');
export default {
fromPartition,
get defaultSession () {
return fromPartition('');
}
};

View File

@@ -1,6 +1,6 @@
import { EventEmitter } from 'events';
import { deprecate } from 'electron';
const { systemPreferences, SystemPreferences } = process._linkedBinding('electron_browser_system_preferences');
const { systemPreferences, SystemPreferences } = process.electronBinding('system_preferences');
// SystemPreferences is an EventEmitter.
Object.setPrototypeOf(SystemPreferences.prototype, EventEmitter.prototype);

View File

@@ -1,12 +1,14 @@
import { EventEmitter } from 'events';
import type { BaseWindow as TLWT } from 'electron';
const { BaseWindow } = process._linkedBinding('electron_browser_base_window') as { BaseWindow: typeof TLWT };
'use strict';
Object.setPrototypeOf(BaseWindow.prototype, EventEmitter.prototype);
const electron = require('electron');
const { EventEmitter } = require('events');
const { TopLevelWindow } = process.electronBinding('top_level_window');
(BaseWindow.prototype as any)._init = function () {
Object.setPrototypeOf(TopLevelWindow.prototype, EventEmitter.prototype);
TopLevelWindow.prototype._init = function () {
// Avoid recursive require.
const { app } = require('electron');
const { app } = electron;
// Simulate the application menu on platforms other than macOS.
if (process.platform !== 'darwin') {
@@ -17,88 +19,88 @@ Object.setPrototypeOf(BaseWindow.prototype, EventEmitter.prototype);
// Properties
Object.defineProperty(BaseWindow.prototype, 'autoHideMenuBar', {
Object.defineProperty(TopLevelWindow.prototype, 'autoHideMenuBar', {
get: function () { return this.isMenuBarAutoHide(); },
set: function (autoHide) { this.setAutoHideMenuBar(autoHide); }
});
Object.defineProperty(BaseWindow.prototype, 'visibleOnAllWorkspaces', {
Object.defineProperty(TopLevelWindow.prototype, 'visibleOnAllWorkspaces', {
get: function () { return this.isVisibleOnAllWorkspaces(); },
set: function (visible) { this.setVisibleOnAllWorkspaces(visible); }
});
Object.defineProperty(BaseWindow.prototype, 'fullScreen', {
Object.defineProperty(TopLevelWindow.prototype, 'fullScreen', {
get: function () { return this.isFullScreen(); },
set: function (full) { this.setFullScreen(full); }
});
Object.defineProperty(BaseWindow.prototype, 'simpleFullScreen', {
Object.defineProperty(TopLevelWindow.prototype, 'simpleFullScreen', {
get: function () { return this.isSimpleFullScreen(); },
set: function (simple) { this.setSimpleFullScreen(simple); }
});
Object.defineProperty(BaseWindow.prototype, 'kiosk', {
Object.defineProperty(TopLevelWindow.prototype, 'kiosk', {
get: function () { return this.isKiosk(); },
set: function (kiosk) { this.setKiosk(kiosk); }
});
Object.defineProperty(BaseWindow.prototype, 'documentEdited', {
Object.defineProperty(TopLevelWindow.prototype, 'documentEdited', {
get: function () { return this.isFullscreen(); },
set: function (edited) { this.setDocumentEdited(edited); }
});
Object.defineProperty(BaseWindow.prototype, 'shadow', {
Object.defineProperty(TopLevelWindow.prototype, 'shadow', {
get: function () { return this.hasShadow(); },
set: function (shadow) { this.setHasShadow(shadow); }
});
Object.defineProperty(BaseWindow.prototype, 'representedFilename', {
Object.defineProperty(TopLevelWindow.prototype, 'representedFilename', {
get: function () { return this.getRepresentedFilename(); },
set: function (filename) { this.setRepresentedFilename(filename); }
});
Object.defineProperty(BaseWindow.prototype, 'minimizable', {
Object.defineProperty(TopLevelWindow.prototype, 'minimizable', {
get: function () { return this.isMinimizable(); },
set: function (min) { this.setMinimizable(min); }
});
Object.defineProperty(BaseWindow.prototype, 'title', {
Object.defineProperty(TopLevelWindow.prototype, 'title', {
get: function () { return this.getTitle(); },
set: function (title) { this.setTitle(title); }
});
Object.defineProperty(BaseWindow.prototype, 'maximizable', {
Object.defineProperty(TopLevelWindow.prototype, 'maximizable', {
get: function () { return this.isMaximizable(); },
set: function (max) { this.setMaximizable(max); }
});
Object.defineProperty(BaseWindow.prototype, 'resizable', {
Object.defineProperty(TopLevelWindow.prototype, 'resizable', {
get: function () { return this.isResizable(); },
set: function (res) { this.setResizable(res); }
});
Object.defineProperty(BaseWindow.prototype, 'menuBarVisible', {
Object.defineProperty(TopLevelWindow.prototype, 'menuBarVisible', {
get: function () { return this.isMenuBarVisible(); },
set: function (visible) { this.setMenuBarVisibility(visible); }
});
Object.defineProperty(BaseWindow.prototype, 'fullScreenable', {
Object.defineProperty(TopLevelWindow.prototype, 'fullScreenable', {
get: function () { return this.isFullScreenable(); },
set: function (full) { this.setFullScreenable(full); }
});
Object.defineProperty(BaseWindow.prototype, 'closable', {
Object.defineProperty(TopLevelWindow.prototype, 'closable', {
get: function () { return this.isClosable(); },
set: function (close) { this.setClosable(close); }
});
Object.defineProperty(BaseWindow.prototype, 'movable', {
Object.defineProperty(TopLevelWindow.prototype, 'movable', {
get: function () { return this.isMovable(); },
set: function (move) { this.setMovable(move); }
});
BaseWindow.getFocusedWindow = () => {
return BaseWindow.getAllWindows().find((win) => win.isFocused());
TopLevelWindow.getFocusedWindow = () => {
return TopLevelWindow.getAllWindows().find((win) => win.isFocused());
};
module.exports = BaseWindow;
module.exports = TopLevelWindow;

5
lib/browser/api/tray.js Normal file
View File

@@ -0,0 +1,5 @@
'use strict';
const { Tray } = process.electronBinding('tray');
module.exports = Tray;

View File

@@ -1,3 +0,0 @@
const { Tray } = process._linkedBinding('electron_browser_tray');
export default Tray;

5
lib/browser/api/view.js Normal file
View File

@@ -0,0 +1,5 @@
'use strict';
const { View } = process.electronBinding('view');
module.exports = View;

View File

@@ -1,3 +0,0 @@
const { View } = process._linkedBinding('electron_browser_view');
export default View;

View File

@@ -0,0 +1,8 @@
const electron = require('electron');
const { View } = electron;
const { ImageView } = process.electronBinding('image_view');
Object.setPrototypeOf(ImageView.prototype, View.prototype);
module.exports = ImageView;

View File

@@ -1,7 +0,0 @@
import { View } from 'electron';
const { ImageView } = process._linkedBinding('electron_browser_image_view');
Object.setPrototypeOf(ImageView.prototype, View.prototype);
export default ImageView;

View File

@@ -0,0 +1,10 @@
'use strict';
const electron = require('electron');
const { View } = electron;
const { WebContentsView } = process.electronBinding('web_contents_view');
Object.setPrototypeOf(WebContentsView.prototype, View.prototype);
module.exports = WebContentsView;

View File

@@ -1,7 +0,0 @@
import { View } from 'electron';
const { WebContentsView } = process._linkedBinding('electron_browser_web_contents_view');
Object.setPrototypeOf(WebContentsView.prototype, View.prototype);
export default WebContentsView;

View File

@@ -1,15 +1,17 @@
import { app, ipcMain, session, deprecate } from 'electron';
import type { MenuItem, MenuItemConstructorOptions, WebContentsInternal } from 'electron';
'use strict';
import * as url from 'url';
import * as path from 'path';
import { internalWindowOpen } from '../guest-window-manager';
import { NavigationController } from '../navigation-controller';
import { ipcMainInternal } from '../ipc-main-internal';
import * as ipcMainUtils from '../ipc-main-internal-utils';
import { parseFeatures } from '../../common/parse-features-string';
import { MessagePortMain } from '../message-port-main';
import { EventEmitter } from 'events';
const { EventEmitter } = require('events');
const electron = require('electron');
const path = require('path');
const url = require('url');
const { app, ipcMain, session } = electron;
const { internalWindowOpen } = require('@electron/internal/browser/guest-window-manager');
const NavigationController = require('@electron/internal/browser/navigation-controller');
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
const { parseFeatures } = require('@electron/internal/common/parse-features-string');
const { MessagePortMain } = require('@electron/internal/browser/message-port-main');
// session is not used here, the purpose is to make sure session is initalized
// before the webContents module.
@@ -21,18 +23,8 @@ const getNextId = function () {
return ++nextId;
};
/* eslint-disable camelcase */
type MediaSize = {
name: string,
custom_display_name: string,
height_microns: number,
width_microns: number,
is_default?: 'true',
}
/* eslint-enable camelcase */
// Stock page sizes
const PDFPageSizes: Record<string, MediaSize> = {
const PDFPageSizes = {
A5: {
custom_display_name: 'A5',
height_microns: 210000,
@@ -75,8 +67,8 @@ const PDFPageSizes: Record<string, MediaSize> = {
// Default printing setting
const defaultPrintingSetting = {
// Customizable.
pageRange: [] as {from: number, to: number}[],
mediaSize: {} as MediaSize,
pageRange: [],
mediaSize: {},
landscape: false,
headerFooterEnabled: false,
marginsType: 0,
@@ -101,18 +93,18 @@ const defaultPrintingSetting = {
copies: 1,
// 2 = color - see ColorModel in //printing/print_job_constants.h
color: 2,
collate: true,
printerType: 2,
title: undefined as string | undefined,
url: undefined as string | undefined
collate: true
};
// JavaScript implementations of WebContents.
const binding = process._linkedBinding('electron_browser_web_contents');
const { WebContents } = binding as { WebContents: { prototype: WebContentsInternal } };
const binding = process.electronBinding('web_contents');
const { WebContents } = binding;
Object.setPrototypeOf(WebContents.prototype, EventEmitter.prototype);
Object.setPrototypeOf(NavigationController.prototype, EventEmitter.prototype);
Object.setPrototypeOf(WebContents.prototype, NavigationController.prototype);
// WebContents::send(channel, args..)
// WebContents::sendToAll(channel, args..)
WebContents.prototype.send = function (channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument');
@@ -131,6 +123,17 @@ WebContents.prototype.postMessage = function (...args) {
this._postMessage(...args);
};
WebContents.prototype.sendToAll = function (channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument');
}
const internal = false;
const sendToAll = true;
return this._send(internal, sendToAll, channel, args);
};
WebContents.prototype._sendInternal = function (channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument');
@@ -182,15 +185,15 @@ const webFrameMethods = [
'insertText',
'removeInsertedCSS',
'setVisualZoomLevelLimits'
] as ('insertCSS' | 'insertText' | 'removeInsertedCSS' | 'setVisualZoomLevelLimits')[];
];
for (const method of webFrameMethods) {
WebContents.prototype[method] = function (...args: any[]): Promise<any> {
WebContents.prototype[method] = function (...args) {
return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, ...args);
};
}
const waitTillCanExecuteJavaScript = async (webContents: WebContentsInternal) => {
const waitTillCanExecuteJavaScript = async (webContents) => {
if (webContents.getURL() && !webContents.isLoadingMainFrame()) return;
return new Promise((resolve) => {
@@ -323,7 +326,7 @@ WebContents.prototype.printToPDF = function (options) {
height_microns: Math.ceil(pageSize.height),
width_microns: Math.ceil(pageSize.width)
};
} else if (Object.prototype.hasOwnProperty.call(PDFPageSizes, pageSize)) {
} else if (PDFPageSizes[pageSize]) {
printSettings.mediaSize = PDFPageSizes[pageSize];
} else {
const error = new Error(`Unsupported pageSize: ${pageSize}`);
@@ -357,14 +360,14 @@ WebContents.prototype.print = function (options = {}, callback) {
throw new Error('height and width properties are required for pageSize');
}
// Dimensions in Microns - 1 meter = 10^6 microns
(options as any).mediaSize = {
options.mediaSize = {
name: 'CUSTOM',
custom_display_name: 'Custom',
height_microns: Math.ceil(pageSize.height),
width_microns: Math.ceil(pageSize.width)
};
} else if (PDFPageSizes[pageSize]) {
(options as any).mediaSize = PDFPageSizes[pageSize];
options.mediaSize = PDFPageSizes[pageSize];
} else {
throw new Error(`Unsupported pageSize: ${pageSize}`);
}
@@ -407,23 +410,23 @@ WebContents.prototype.loadFile = function (filePath, options = {}) {
}));
};
const addReplyToEvent = (event: any) => {
event.reply = (...args: any[]) => {
const addReplyToEvent = (event) => {
event.reply = (...args) => {
event.sender.sendToFrame(event.frameId, ...args);
};
};
const addReplyInternalToEvent = (event: any) => {
const addReplyInternalToEvent = (event) => {
Object.defineProperty(event, '_replyInternal', {
configurable: false,
enumerable: false,
value: (...args: any[]) => {
value: (...args) => {
event.sender._sendToFrameInternal(event.frameId, ...args);
}
});
};
const addReturnValueToEvent = (event: any) => {
const addReturnValueToEvent = (event) => {
Object.defineProperty(event, 'returnValue', {
set: (value) => event.sendReply([value]),
get: () => {}
@@ -433,30 +436,14 @@ const addReturnValueToEvent = (event: any) => {
// Add JavaScript wrappers for WebContents class.
WebContents.prototype._init = function () {
// The navigation controller.
const navigationController = new NavigationController(this);
this.loadURL = navigationController.loadURL.bind(navigationController);
this.getURL = navigationController.getURL.bind(navigationController);
this.stop = navigationController.stop.bind(navigationController);
this.reload = navigationController.reload.bind(navigationController);
this.reloadIgnoringCache = navigationController.reloadIgnoringCache.bind(navigationController);
this.canGoBack = navigationController.canGoBack.bind(navigationController);
this.canGoForward = navigationController.canGoForward.bind(navigationController);
this.canGoToIndex = navigationController.canGoToIndex.bind(navigationController);
this.canGoToOffset = navigationController.canGoToOffset.bind(navigationController);
this.clearHistory = navigationController.clearHistory.bind(navigationController);
this.goBack = navigationController.goBack.bind(navigationController);
this.goForward = navigationController.goForward.bind(navigationController);
this.goToIndex = navigationController.goToIndex.bind(navigationController);
this.goToOffset = navigationController.goToOffset.bind(navigationController);
this.getActiveIndex = navigationController.getActiveIndex.bind(navigationController);
this.length = navigationController.length.bind(navigationController);
NavigationController.call(this, this);
// Every remote callback from renderer process would add a listener to the
// render-view-deleted event, so ignore the listeners warning.
this.setMaxListeners(0);
// Dispatch IPC messages to the ipc module.
this.on('-ipc-message' as any, function (this: WebContentsInternal, event: any, internal: boolean, channel: string, args: any[]) {
this.on('-ipc-message', function (event, internal, channel, args) {
if (internal) {
addReplyInternalToEvent(event);
ipcMainInternal.emit(channel, event, ...args);
@@ -467,21 +454,21 @@ WebContents.prototype._init = function () {
}
});
this.on('-ipc-invoke' as any, function (event: any, internal: boolean, channel: string, args: any[]) {
event._reply = (result: any) => event.sendReply({ result });
event._throw = (error: Error) => {
this.on('-ipc-invoke', function (event, internal, channel, args) {
event._reply = (result) => event.sendReply({ result });
event._throw = (error) => {
console.error(`Error occurred in handler for '${channel}':`, error);
event.sendReply({ error: error.toString() });
};
const target = internal ? ipcMainInternal : ipcMain;
if ((target as any)._invokeHandlers.has(channel)) {
(target as any)._invokeHandlers.get(channel)(event, ...args);
if (target._invokeHandlers.has(channel)) {
target._invokeHandlers.get(channel)(event, ...args);
} else {
event._throw(`No handler registered for '${channel}'`);
}
});
this.on('-ipc-message-sync' as any, function (this: WebContentsInternal, event: any, internal: boolean, channel: string, args: any[]) {
this.on('-ipc-message-sync', function (event, internal, channel, args) {
addReturnValueToEvent(event);
if (internal) {
addReplyInternalToEvent(event);
@@ -493,15 +480,15 @@ WebContents.prototype._init = function () {
}
});
this.on('-ipc-ports' as any, function (event: any, internal: boolean, channel: string, message: any, ports: any[]) {
this.on('-ipc-ports', function (event, internal, channel, message, ports) {
event.ports = ports.map(p => new MessagePortMain(p));
ipcMain.emit(channel, event, message);
});
// Handle context menu action request from pepper plugin.
this.on('pepper-context-menu' as any, function (event: any, params: {x: number, y: number, menu: Array<(MenuItemConstructorOptions) | (MenuItem)>}, callback: () => void) {
this.on('pepper-context-menu', function (event, params, callback) {
// Access Menu via electron.Menu to prevent circular require.
const menu = require('electron').Menu.buildFromTemplate(params.menu);
const menu = electron.Menu.buildFromTemplate(params.menu);
menu.popup({
window: event.sender.getOwnerBrowserWindow(),
x: params.x,
@@ -519,14 +506,14 @@ WebContents.prototype._init = function () {
});
// The devtools requests the webContents to reload.
this.on('devtools-reload-page', function (this: WebContentsInternal) {
this.on('devtools-reload-page', function () {
this.reload();
});
if (this.getType() !== 'remote') {
// Make new windows requested by links behave like "window.open".
this.on('-new-window' as any, (event: any, url: string, frameName: string, disposition: string,
rawFeatures: string, referrer: string, postData: string) => {
this.on('-new-window', (event, url, frameName, disposition,
rawFeatures, referrer, postData) => {
const { options, webPreferences, additionalFeatures } = parseFeatures(rawFeatures);
const mergedOptions = {
show: true,
@@ -542,9 +529,9 @@ WebContents.prototype._init = function () {
// Create a new browser window for the native implementation of
// "window.open", used in sandbox and nativeWindowOpen mode.
this.on('-add-new-contents' as any, (event: any, webContents: WebContentsInternal, disposition: string,
userGesture: boolean, left: number, top: number, width: number, height: number, url: string, frameName: string,
referrer: string, rawFeatures: string, postData: string) => {
this.on('-add-new-contents', (event, webContents, disposition,
userGesture, left, top, width, height, url, frameName,
referrer, rawFeatures, postData) => {
if ((disposition !== 'foreground-tab' && disposition !== 'new-window' &&
disposition !== 'background-tab')) {
event.preventDefault();
@@ -567,7 +554,7 @@ WebContents.prototype._init = function () {
const prefs = this.getWebPreferences() || {};
if (prefs.webviewTag && prefs.contextIsolation) {
deprecate.log('Security Warning: A WebContents was just created with both webviewTag and contextIsolation enabled. This combination is fundamentally less secure and effectively bypasses the protections of contextIsolation. We strongly recommend you move away from webviews to OOPIF or BrowserView in order for your app to be more secure');
electron.deprecate.log('Security Warning: A WebContents was just created with both webviewTag and contextIsolation enabled. This combination is fundamentally less secure and effectively bypasses the protections of contextIsolation. We strongly recommend you move away from webviews to OOPIF or BrowserView in order for your app to be more secure');
}
}
@@ -575,7 +562,7 @@ WebContents.prototype._init = function () {
app.emit('login', event, this, ...args);
});
const event = process._linkedBinding('electron_browser_event').createEmpty();
const event = process.electronBinding('event').createEmpty();
app.emit('web-contents-created', event, this);
// Properties
@@ -612,25 +599,28 @@ WebContents.prototype._init = function () {
};
// Public APIs.
export function create (options = {}) {
return binding.create(options);
}
module.exports = {
create (options = {}) {
return binding.create(options);
},
export function fromId (id: string) {
return binding.fromId(id);
}
fromId (id) {
return binding.fromId(id);
},
export function getFocusedWebContents () {
let focused = null;
for (const contents of binding.getAllWebContents()) {
if (!contents.isFocused()) continue;
if (focused == null) focused = contents;
// Return webview web contents which may be embedded inside another
// web contents that is also reporting as focused
if (contents.getType() === 'webview') return contents;
getFocusedWebContents () {
let focused = null;
for (const contents of binding.getAllWebContents()) {
if (!contents.isFocused()) continue;
if (focused == null) focused = contents;
// Return webview web contents which may be embedded inside another
// web contents that is also reporting as focused
if (contents.getType() === 'webview') return contents;
}
return focused;
},
getAllWebContents () {
return binding.getAllWebContents();
}
return focused;
}
export function getAllWebContents () {
return binding.getAllWebContents();
}
};

View File

@@ -1,21 +1,23 @@
'use strict';
// This is a temporary shim to aid in transition from the old
// BrowserWindow-based extensions stuff to the new native-backed extensions
// API.
import { app, session, BrowserWindow, deprecate } from 'electron';
const { app, session, BrowserWindow, deprecate } = require('electron');
app.whenReady().then(function () {
const addExtension = function (srcDirectory: string) {
const addExtension = function (srcDirectory) {
return session.defaultSession.loadExtension(srcDirectory);
};
const removeExtension = function (name: string) {
const removeExtension = function (name) {
const extension = session.defaultSession.getAllExtensions().find(e => e.name === name);
if (extension) { session.defaultSession.removeExtension(extension.id); }
};
const getExtensions = function () {
const extensions: Record<string, any> = {};
const extensions = {};
session.defaultSession.getAllExtensions().forEach(e => {
extensions[e.name] = e;
});

View File

@@ -1,6 +1,6 @@
import { shell, Menu } from 'electron';
const v8Util = process._linkedBinding('electron_common_v8_util');
const v8Util = process.electronBinding('v8_util');
const isMac = process.platform === 'darwin';

View File

@@ -1,7 +1,4 @@
const {
createDesktopCapturer,
getMediaSourceIdForWebContents: getMediaSourceIdForWebContentsBinding
} = process._linkedBinding('electron_browser_desktop_capturer');
const { createDesktopCapturer } = process.electronBinding('desktop_capturer');
const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) => JSON.stringify(a) === JSON.stringify(b);
@@ -80,7 +77,3 @@ export const getSourcesImpl = (event: Electron.IpcMainEvent | null, args: Electr
return getSources;
};
export const getMediaSourceIdForWebContents = (event: Electron.IpcMainEvent, webContentsId: number) => {
return getMediaSourceIdForWebContentsBinding(event.sender.id, webContentsId);
};

View File

@@ -59,7 +59,7 @@ function sanitizeOptionsForGuest (options) {
// Create a new guest instance.
const createGuest = function (embedder, params) {
if (webViewManager == null) {
webViewManager = process._linkedBinding('electron_browser_web_view_manager');
webViewManager = process.electronBinding('web_view_manager');
}
const guest = webContents.create({

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